From 7dc66d660a369afe94bce3d3862a88863bc0d1a3 Mon Sep 17 00:00:00 2001 From: ruchirguitar <113586074+ruchirguitar@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:24:08 +0530 Subject: [PATCH] Fix #5526: select images by link status Selection Tools add-on: addon.select.byLinkStatus selects the image objects and placed documents whose link is linked, embedded, missing or modified (one status or several), as links.list reports it. Optional ids limit the search, add adds to the selection, and zoom runs the existing Zoom to Selection in the desktop app. Objects that are hidden, locked, on a template layer or outside the isolated group are left out and returned with the reason. Only the selection changes. Select Linked / Embedded / Missing / Modified are in Object > Plug-ins > Selection Tools and the Links panel menu. Each is off when no image has that status, and Shift adds to the selection. Select by Link Status... lists the images with name, status and format, with a name search, a status filter, checkboxes with All / None and Zoom to Selection. The status bar says what was selected and how many were left out, with plural rows in every catalog. Tests: engine (statuses, ids, add, zoom, skip reasons, bad params), command sweep, and UI (message, list filtering, menu items, Links panel menu, headless dialog frame). Docs: mcp, target-app-parity, ui-parity, roadmap. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/addons/select.rs | 156 +++++++++- crates/engine/src/cmd/addons/select/tests.rs | 176 +++++++++++ crates/engine/src/cmd/links.rs | 2 +- crates/engine/tests/command_sweep.rs | 4 + crates/ui-egui/src/addons/select.rs | 9 +- .../ui-egui/src/addons/select/link_status.rs | 286 ++++++++++++++++++ .../src/addons/select/link_status/tests.rs | 199 ++++++++++++ crates/ui-egui/src/app.rs | 1 + crates/ui-egui/src/dialogs.rs | 1 + crates/ui-egui/src/i18n/bg.tsv | 19 ++ crates/ui-egui/src/i18n/bs.tsv | 19 ++ crates/ui-egui/src/i18n/ca.tsv | 19 ++ crates/ui-egui/src/i18n/cs.tsv | 19 ++ crates/ui-egui/src/i18n/da.tsv | 19 ++ crates/ui-egui/src/i18n/de.tsv | 19 ++ crates/ui-egui/src/i18n/el.tsv | 19 ++ crates/ui-egui/src/i18n/es.tsv | 19 ++ crates/ui-egui/src/i18n/fi.tsv | 19 ++ crates/ui-egui/src/i18n/fr.tsv | 19 ++ crates/ui-egui/src/i18n/hu.tsv | 19 ++ crates/ui-egui/src/i18n/id.tsv | 19 ++ crates/ui-egui/src/i18n/it.tsv | 19 ++ crates/ui-egui/src/i18n/ja.tsv | 19 ++ crates/ui-egui/src/i18n/ka.tsv | 19 ++ crates/ui-egui/src/i18n/ko.tsv | 19 ++ crates/ui-egui/src/i18n/nl.tsv | 19 ++ crates/ui-egui/src/i18n/pl.tsv | 19 ++ crates/ui-egui/src/i18n/pt-br.tsv | 19 ++ crates/ui-egui/src/i18n/ru.tsv | 19 ++ crates/ui-egui/src/i18n/sv.tsv | 19 ++ crates/ui-egui/src/i18n/tr.tsv | 19 ++ crates/ui-egui/src/i18n/uk.tsv | 19 ++ crates/ui-egui/src/i18n/vi.tsv | 19 ++ crates/ui-egui/src/i18n/zh-hans.tsv | 19 ++ crates/ui-egui/src/i18n/zh-hant.tsv | 19 ++ crates/ui-egui/src/menus.rs | 2 + crates/ui-egui/src/menus/invoke.rs | 7 +- crates/ui-egui/src/menus/run.rs | 1 + crates/ui-egui/src/menus/state.rs | 2 +- crates/ui-egui/src/panels/links.rs | 14 +- crates/ui-egui/src/state.rs | 6 + docs/mcp.md | 24 ++ docs/roadmap.md | 3 +- docs/target-app-parity.md | 4 +- docs/ui-parity.md | 4 +- 45 files changed, 1370 insertions(+), 25 deletions(-) create mode 100644 crates/ui-egui/src/addons/select/link_status.rs create mode 100644 crates/ui-egui/src/addons/select/link_status/tests.rs diff --git a/crates/engine/src/cmd/addons/select.rs b/crates/engine/src/cmd/addons/select.rs index ce2869abc..4d71895c0 100644 --- a/crates/engine/src/cmd/addons/select.rs +++ b/crates/engine/src/cmd/addons/select.rs @@ -1,31 +1,51 @@ //! Selection Tools (`addon.select.*`), a built-in add-on: Select by Artboards selects the objects -//! overlapping, fully inside or fully outside one or more chosen artboards (#5525). Its submenu -//! (Object › Plug-ins › Selection Tools) and dialog are the UI's. +//! overlapping, fully inside or fully outside one or more chosen artboards (#5525), and Select by +//! Link Status selects the images and placed documents whose file is linked, embedded, missing or +//! modified (#5526), saying which ones it had to leave out and why. Its submenu (Object › Plug-ins +//! › Selection Tools), the Links panel's menu items and the dialogs are the UI's. #[cfg(test)] mod tests; +use std::collections::BTreeSet; + use serde_json::{Value, json}; -use vectorcraft_doc::NodeId; +use vectorcraft_doc::{LockedArtboards, Node, NodeId, NodeKind}; use vectorcraft_geom::Point; -use super::super::select::{on_artboard, selectable}; -use super::super::{CommandSpec, bad, cmd, has_doc, point_indices, text_param}; -use crate::{Result, Session}; +use super::super::links::{Status, statuses}; +use super::super::select::{is_selectable, on_artboard, selectable}; +use super::super::{CommandSpec, bad, checked_ids_param, cmd, has_doc, point_indices, switch_or, text_param}; +use crate::{DocState, Result, Session}; /// How Select by Artboards relates an object to the chosen artboards (`relation`). pub const RELATIONS: [&str; 3] = ["overlap", "inside", "outside"]; +/// The link statuses Select by Link Status takes (`status`): a file found unchanged, pixels kept +/// in the document, a file not found, a file changed since it was read. +pub const LINK_STATUSES: [&str; 4] = ["linked", "embedded", "missing", "modified"]; + pub fn specs() -> Vec { - vec![cmd!( - "addon.select.byArtboards", - "Select by Artboards", - ["Object", "Plug-ins", "Selection Tools"], - None, - "{artboards: [index…] (0-based, at least one), relation?: \"overlap\" (default: on at least one of them, as select.allOnArtboard takes it: the bounds overlap the artboard or the centre is on it) | \"inside\" (the bounds lie wholly within one of them, edges included) | \"outside\" (on none of them)} select those objects among the ones Select All takes (in isolation mode, the isolated container's); hidden and locked objects, objects on hidden, locked or template layers and art held by a locked artboard are left out. Bounds are geometric, a symbol instance's measured by its art → {count}", - has_doc, - by_artboards - )] + vec![ + cmd!( + "addon.select.byArtboards", + "Select by Artboards", + ["Object", "Plug-ins", "Selection Tools"], + None, + "{artboards: [index…] (0-based, at least one), relation?: \"overlap\" (default: on at least one of them, as select.allOnArtboard takes it: the bounds overlap the artboard or the centre is on it) | \"inside\" (the bounds lie wholly within one of them, edges included) | \"outside\" (on none of them)} select those objects among the ones Select All takes (in isolation mode, the isolated container's); hidden and locked objects, objects on hidden, locked or template layers and art held by a locked artboard are left out. Bounds are geometric, a symbol instance's measured by its art → {count}", + has_doc, + by_artboards + ), + cmd!( + "addon.select.byLinkStatus", + "Select by Link Status", + ["Object", "Plug-ins", "Selection Tools"], + None, + "{status: \"linked\" (its file found unchanged) | \"embedded\" | \"missing\" | \"modified\" (its file changed since it was read) | [several of them] (at least one), ids?: [object ids] (pick only among these; default: every image and placed document in the layers), add?: false (add to the selection instead of replacing it), zoom?: false (then zoom to the selection: the desktop app runs addon.arrange.zoomSelection when something was selected; elsewhere the result's zoom flag asks the caller to)} select the image objects and placed documents whose link has that status, as links.list reports it. Those that are hidden or locked, inside a hidden or locked object or layer, on a template layer, on a locked artboard while nothing is isolated, or outside the isolated group or layer are left out and listed with the reason. Only the selection changes, not the document → {count (how many it selected), selected: [ids] (in stacking order, bottom first), skipped: [{id, reason: hidden|locked|template|isolation}], zoom?: true}", + has_doc, + by_link_status + ), + ] } fn by_artboards(s: &mut Session, p: &Value) -> Result { @@ -61,3 +81,109 @@ fn by_artboards(s: &mut Session, p: &Value) -> Result { s.select(|_, sel| sel.set(ids))?; Ok(json!({ "count": s.doc()?.selection.objects.len() })) } + +/// The link statuses `p` asks for (`status`: one, or a list of them), each once. +fn link_statuses(p: &Value, cmd: &str) -> Result> { + let given: Vec<&Value> = match p.get("status") { + None | Some(Value::Null) => return Err(bad(cmd, "missing `status`: linked, embedded, missing or modified")), + Some(Value::Array(v)) => v.iter().collect(), + Some(v) => vec![v], + }; + let mut out = vec![]; + for v in given { + let s = v.as_str().ok_or_else(|| bad(cmd, "`status` must be linked, embedded, missing or modified, or a list of them"))?; + let known = + LINK_STATUSES.iter().find(|k| **k == s).ok_or_else(|| bad(cmd, format!("status `{s}`: linked, embedded, missing or modified")))?; + if !out.contains(known) { + out.push(*known); + } + } + if out.is_empty() { + return Err(bad(cmd, "choose at least one status")); + } + Ok(out) +} + +/// The link status of every image object and placed document in the layers of `st`, as +/// `links.list` reports it (`ok` named `linked`), in stacking order (bottom first). Each file is +/// looked at once. +pub(crate) fn link_status_of_objects(st: &DocState) -> Vec<(NodeId, &'static str)> { + let linked = statuses(st); + let mut out = vec![]; + st.doc.walk(|n| { + if matches!(n.kind, NodeKind::Image(_) | NodeKind::PlacedDocument(_)) { + let status = match linked.get(&n.id).map(|(s, ..)| *s) { + Some(Status::Ok) => "linked", + Some(Status::Modified) => "modified", + Some(Status::Missing) => "missing", + None => "embedded", + }; + out.push((n.id, status)); + } + }); + out +} + +/// Why object `id` of `st` can't be selected now ([`is_selectable`]): it is outside the isolated +/// group or layer, on a template layer, hidden (or inside something hidden), or locked (or inside +/// something locked, or on a locked artboard); `None` when it can be. `locks` is the document's +/// [`vectorcraft_doc::Document::locked_artboards`]. +fn skip_reason(st: &DocState, locks: &LockedArtboards, id: NodeId) -> Option<&'static str> { + let d = &st.doc; + if is_selectable(d, st.isolation, locks, id) { + return None; + } + let ancestry = d.ancestry(id).unwrap_or_default(); + // Outside the isolated container, or the isolated container itself (its art is what's taken). + if let Some(c) = st.isolation.and_then(|i| d.node(i)) + && ancestry.iter().position(|i| *i == c.id).is_none_or(|at| at + 1 == ancestry.len() && c.is_container()) + { + return Some("isolation"); + } + let held: Vec<&Node> = ancestry.iter().filter_map(|i| d.node(*i)).collect(); + Some(if held.iter().any(|n| n.is_template()) { + "template" + } else if held.iter().any(|n| !n.visible) { + "hidden" + } else { + // A locked object, group or layer, or the locked artboard that holds it. + "locked" + }) +} + +fn by_link_status(s: &mut Session, p: &Value) -> Result { + const C: &str = "addon.select.byLinkStatus"; + let wanted = link_statuses(p, C)?; + let add = switch_or(p, "add", false, C)?; + let zoom = switch_or(p, "zoom", false, C)?; + let only: Option> = match p.get("ids") { + None | Some(Value::Null) => None, + Some(_) => Some(checked_ids_param(s, p, "ids", C)?.into_iter().collect()), + }; + let st = s.doc()?; + let locks = st.doc.locked_artboards(); + let (mut picked, mut skipped) = (vec![], vec![]); + for (id, status) in link_status_of_objects(st) { + if !wanted.contains(&status) || only.as_ref().is_some_and(|o| !o.contains(&id)) { + continue; + } + match skip_reason(st, &locks, id) { + None => picked.push(id), + Some(reason) => skipped.push(json!({ "id": id.0, "reason": reason })), + } + } + s.select(|_, sel| { + if add { + for id in &picked { + sel.add(*id); + } + } else { + sel.set(picked.iter().copied()); + } + })?; + let mut out = json!({ "count": picked.len(), "selected": picked.iter().map(|id| id.0).collect::>(), "skipped": skipped }); + if zoom { + out["zoom"] = json!(true); + } + Ok(out) +} diff --git a/crates/engine/src/cmd/addons/select/tests.rs b/crates/engine/src/cmd/addons/select/tests.rs index bea05ff9e..ca5956526 100644 --- a/crates/engine/src/cmd/addons/select/tests.rs +++ b/crates/engine/src/cmd/addons/select/tests.rs @@ -126,3 +126,179 @@ fn a_symbol_instance_is_measured_by_its_art() { assert_eq!(pick(&mut s, json!({"artboards": [0], "relation": "inside"})), vec![instance]); assert_eq!(pick(&mut s, json!({"artboards": [0], "relation": "outside"})), vec![]); } + +/// Select by Link Status. +mod by_link_status { + use serde_json::{Value, json}; + use vectorcraft_doc::{Document, NodeId}; + + use crate::Session; + use crate::tests_links::{BLUE, Folder, RED, place, png, session, write}; + + const C: &str = "addon.select.byLinkStatus"; + + /// One linked image whose file stays as it is (`ok`), one whose file changes (`modified`), + /// one whose file goes (`missing`), an embedded one, and a placed document whose file goes + /// (missing too), bottom to top. + struct Links { + s: Session, + ok: NodeId, + modified: NodeId, + missing: NodeId, + embedded: NodeId, + document: NodeId, + _dir: Folder, + } + + fn links(name: &str) -> Links { + let dir = Folder::new(name); + let (a, b, c, e, d) = (dir.file("ok.png"), dir.file("changes.png"), dir.file("goes.png"), dir.file("kept.png"), dir.file("page.vectorcraft")); + write(&a, &png(10, 10, RED)); + write(&b, &png(12, 10, RED)); + write(&c, &png(14, 10, BLUE)); + write(&e, &png(16, 10, BLUE)); + write(&d, &vectorcraft_format::save_file(&Document::new(80.0, 50.0))); + let mut s = session(); + let (ok, modified, missing) = (place(&mut s, &a), place(&mut s, &b), place(&mut s, &c)); + let embedded = NodeId(s.execute("file.place", &json!({"path": e, "at": [100, 100], "link": false})).unwrap()["ids"][0].as_u64().unwrap()); + let document = place(&mut s, &d); + write(&b, &png(13, 10, BLUE)); + std::fs::remove_file(&c).unwrap(); + std::fs::remove_file(&d).unwrap(); + Links { s, ok, modified, missing, embedded, document, _dir: dir } + } + + fn run(s: &mut Session, p: Value) -> Value { + s.execute(C, &p).unwrap_or_else(|e| panic!("{p}: {e}")) + } + + fn sorted(mut v: Vec) -> Vec { + v.sort(); + v + } + + fn selection(s: &Session) -> Vec { + sorted(s.doc().unwrap().selection.objects.clone()) + } + + fn ids(v: &Value) -> Vec { + sorted(v.as_array().unwrap().iter().map(|i| NodeId(i.as_u64().unwrap())).collect()) + } + + #[test] + fn each_status_selects_its_images_and_placed_documents() { + let Links { mut s, ok, modified, missing, embedded, document, _dir } = links("select-status"); + // As the Links panel lists them. + let listed = s.execute("links.list", &json!({})).unwrap(); + let status = |id: NodeId| listed["links"].as_array().unwrap().iter().find(|r| r["id"] == id.0).unwrap()["status"].clone(); + assert_eq!([ok, modified, missing, embedded, document].map(status), ["ok", "modified", "missing", "embedded", "missing"].map(|s| json!(s))); + let undo = s.doc().unwrap().history.undo.len(); + for (status, want) in [("linked", vec![ok]), ("modified", vec![modified]), ("missing", vec![missing, document]), ("embedded", vec![embedded])] + { + s.execute("select.none", &json!({})).unwrap(); + let r = run(&mut s, json!({"status": status})); + assert_eq!((ids(&r["selected"]), r["count"].as_u64()), (sorted(want.clone()), Some(want.len() as u64)), "{status}: {r}"); + assert_eq!(selection(&s), sorted(want), "{status}"); + assert_eq!(r["skipped"], json!([])); + assert!(r.get("zoom").is_none()); + } + // Several at once, in stacking order (bottom first); a status listed twice counts once. + let r = run(&mut s, json!({"status": ["missing", "linked", "missing"]})); + assert_eq!(r["selected"], json!([ok.0, missing.0, document.0])); + assert_eq!(s.doc().unwrap().history.undo.len(), undo, "only the selection changes"); + } + + #[test] + fn ids_add_and_zoom() { + let Links { mut s, ok, modified, embedded, document, _dir, .. } = links("select-options"); + // Only among the ids given; other objects among them (a rectangle) are passed over. + let rect = s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 5, "height": 5})).unwrap()["id"].as_u64().unwrap(); + let r = run(&mut s, json!({"status": "missing", "ids": [document.0, ok.0, rect]})); + assert_eq!(r["selected"], json!([document.0])); + assert_eq!(selection(&s), vec![document]); + // Added to the selection. + let r = run(&mut s, json!({"status": "embedded", "add": true})); + assert_eq!((r["count"].as_u64(), r["selected"].clone()), (Some(1), json!([embedded.0]))); + assert_eq!(selection(&s), sorted(vec![document, embedded])); + // Replacing it by default. + run(&mut s, json!({"status": "modified"})); + assert_eq!(selection(&s), vec![modified]); + // Nothing to select: added, the selection stays; replacing, it empties. + let r = run(&mut s, json!({"status": "linked", "ids": [embedded.0], "add": true})); + assert_eq!((r["count"].as_u64(), r["selected"].clone(), r["skipped"].clone()), (Some(0), json!([]), json!([]))); + assert_eq!(selection(&s), vec![modified]); + run(&mut s, json!({"status": "linked", "ids": [embedded.0]})); + assert_eq!(selection(&s), vec![]); + // Zoom is reported for the caller (the desktop app zooms). + assert_eq!(run(&mut s, json!({"status": "linked", "zoom": true}))["zoom"], true); + assert!(run(&mut s, json!({"status": "linked", "zoom": false})).get("zoom").is_none()); + } + + #[test] + fn hidden_locked_template_and_isolated_ones_are_skipped_with_the_reason() { + let dir = Folder::new("select-skipped"); + let files: Vec = (0..6).map(|i| dir.file(&format!("img{i}.png"))).collect(); + for (i, f) in files.iter().enumerate() { + write(f, &png(10 + i as u32, 10, RED)); + } + let mut s = session(); + let [free, hidden, locked, in_group, mate] = [0, 1, 2, 3, 4].map(|i| place(&mut s, &files[i])); + s.execute("object.setProps", &json!({"ids": [hidden.0], "visible": false})).unwrap(); + s.execute("object.setProps", &json!({"ids": [locked.0], "locked": true})).unwrap(); + // Inside a locked group. + s.execute("select.set", &json!({"ids": [in_group.0]})).unwrap(); + let g = s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap(); + s.execute("object.setProps", &json!({"ids": [g], "locked": true})).unwrap(); + // On a template layer. + let layer = s.execute("layer.new", &json!({"name": "Trace"})).unwrap()["id"].as_u64().unwrap(); + let on_template = place(&mut s, &files[5]); + s.execute("layer.setProps", &json!({"id": layer, "template": true})).unwrap(); + let r = run(&mut s, json!({"status": "linked"})); + assert_eq!(ids(&r["selected"]), sorted(vec![free, mate]), "{r}"); + let mut reasons: Vec<(u64, String)> = + r["skipped"].as_array().unwrap().iter().map(|k| (k["id"].as_u64().unwrap(), k["reason"].as_str().unwrap().to_string())).collect(); + reasons.sort(); + let mut want: Vec<(u64, String)> = [(hidden, "hidden"), (locked, "locked"), (in_group, "locked"), (on_template, "template")] + .into_iter() + .map(|(id, why)| (id.0, why.to_string())) + .collect(); + want.sort(); + assert_eq!(reasons, want, "{r}"); + assert!(reasons.iter().all(|(_, why)| ["hidden", "locked", "template", "isolation"].contains(&why.as_str()))); + // In isolation mode, those outside the isolated group are left out. + s.execute("select.set", &json!({"ids": [mate.0]})).unwrap(); + let iso = s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap(); + s.execute("object.isolate", &json!({"id": iso})).unwrap(); + let r = run(&mut s, json!({"status": "linked"})); + assert_eq!(r["selected"], json!([mate.0]), "{r}"); + let why = |id: NodeId| r["skipped"].as_array().unwrap().iter().find(|k| k["id"] == id.0).map(|k| k["reason"].clone()); + assert_eq!(why(free), Some(json!("isolation"))); + assert_eq!(why(hidden), Some(json!("isolation"))); + } + + #[test] + fn bad_params_are_errors_and_leave_the_selection() { + let Links { mut s, ok, _dir, .. } = links("select-bad"); + for p in [ + json!({}), + json!({"status": null}), + json!({"status": []}), + json!({"status": "lost"}), + json!({"status": ["linked", "lost"]}), + json!({"status": 3}), + json!({"status": [true]}), + json!({"status": "linked", "add": "yes"}), + json!({"status": "linked", "zoom": 1}), + json!({"status": "linked", "ids": "1"}), + json!({"status": "linked", "ids": [999_999]}), + json!({"status": "linked", "ids": [-1]}), + json!({"status": "linked", "stauts": "missing"}), + ] { + s.execute("select.set", &json!({"ids": [ok.0]})).unwrap(); + assert!(s.execute(C, &p).is_err(), "{p}"); + assert_eq!(s.doc().unwrap().selection.objects, vec![ok], "{p}"); + } + // Without a document it is off, not a crash. + assert!(Session::new().execute(C, &json!({"status": "linked"})).is_err()); + } +} diff --git a/crates/engine/src/cmd/links.rs b/crates/engine/src/cmd/links.rs index 441249b05..842d78b56 100644 --- a/crates/engine/src/cmd/links.rs +++ b/crates/engine/src/cmd/links.rs @@ -1110,7 +1110,7 @@ fn linked_param(st: &crate::DocState, p: &Value, cmd: &str) -> Result { /// Per linked image in the layers of `st`: its file's status, where it was found when away from /// its path, and whether the image shows its preview (each file probed once). -fn statuses(st: &crate::DocState) -> BTreeMap, bool)> { +pub(crate) fn statuses(st: &crate::DocState) -> BTreeMap, bool)> { let mut out = BTreeMap::new(); for g in groups(&st.doc, None).iter().filter(|g| !g.ids.is_empty()) { let (status, found) = probe(g, st.path.as_deref()); diff --git a/crates/engine/tests/command_sweep.rs b/crates/engine/tests/command_sweep.rs index b8b360b80..42970aa16 100644 --- a/crates/engine/tests/command_sweep.rs +++ b/crates/engine/tests/command_sweep.rs @@ -239,6 +239,10 @@ fn structured_junk() { ("addon.select.byArtboards", json!({"artboards": [0, u64::MAX, -1, "x", null, 1.5], "relation": "inside"})), ("addon.select.byArtboards", json!({"artboards": [1e308], "relation": [1]})), ("addon.select.byArtboards", json!({"artboards": {"a": 1}, "relation": "outside"})), + ("addon.select.byLinkStatus", json!({"status": ["linked", "missing", null, 7, {"a": 1}], "ids": [2, u64::MAX, -1, "x", 1.5]})), + ("addon.select.byLinkStatus", json!({"status": "embedded", "ids": {"a": 1}, "add": [1], "zoom": "yes"})), + ("addon.select.byLinkStatus", json!({"status": ["modified", "embedded"], "ids": [], "add": true, "zoom": true})), + ("addon.select.byLinkStatus", json!({"status": [[["linked"]]], "ids": null})), ("object.transform", json!({"matrix": [0, 0, 0, 0, 0, 0]})), ("object.transform", json!({"matrix": [1e308, 1e308, 1e308, 1e308, 1e308, 1e308]})), ("object.transform", json!({"matrix": [1, 2, 3]})), diff --git a/crates/ui-egui/src/addons/select.rs b/crates/ui-egui/src/addons/select.rs index 8f6b25826..c61eb0537 100644 --- a/crates/ui-egui/src/addons/select.rs +++ b/crates/ui-egui/src/addons/select.rs @@ -1,7 +1,8 @@ //! Selection Tools in the interface: its submenu (Object › Plug-ins › Selection Tools) and the //! Select by Artboards dialog (#5548), which lists the artboards with their number, name and size, //! filters them by name, picks several, fits one in the window on double-click (#5549), and on OK -//! runs `addon.select.byArtboards` with the relation chosen. +//! runs `addon.select.byArtboards` with the relation chosen; and Select by Link Status +//! ([`link_status`], #5526). use egui::{Sense, Ui, vec2}; use serde_json::{Value, json}; @@ -13,6 +14,8 @@ use crate::state::Dialog; use crate::theme::Tokens; use crate::widgets; +pub mod link_status; + /// Select by Artboards, and its dialog's kind. pub const COMMAND: &str = "addon.select.byArtboards"; pub const KIND: &str = "selectByArtboards"; @@ -25,7 +28,9 @@ const LIST_H: f32 = 220.0; /// Object › Plug-ins › Selection Tools. pub fn menu() -> Vec { - vec![Item::Cmd("Select by Artboards…", COMMAND, Value::Null)] + let mut v = vec![Item::Cmd("Select by Artboards…", COMMAND, Value::Null), Item::Sep]; + v.extend(link_status::menu()); + v } /// The dialog's fields to start with: the active artboard chosen, Overlapping. diff --git a/crates/ui-egui/src/addons/select/link_status.rs b/crates/ui-egui/src/addons/select/link_status.rs new file mode 100644 index 000000000..7b7203276 --- /dev/null +++ b/crates/ui-egui/src/addons/select/link_status.rs @@ -0,0 +1,286 @@ +//! Select by Link Status in the interface (#5526): Select Linked, Embedded, Missing and Modified +//! (in Object › Plug-ins › Selection Tools and the Links panel's menu; Shift adds to the +//! selection), the Select by Link Status dialog, and the status bar's line saying what was +//! selected and what was left out. Everything goes through `addon.select.byLinkStatus`; the dialog +//! lists what `links.list` reports. + +use egui::{Ui, vec2}; +use serde_json::{Value, json}; + +use crate::VectorcraftApp; +use crate::dialogs::{DialogSpec, form}; +use crate::menus::Item; +use crate::state::Dialog; +use crate::theme::Tokens; +use crate::widgets; + +/// Select by Link Status, and its dialog's kind. +pub const COMMAND: &str = "addon.select.byLinkStatus"; +pub const KIND: &str = "selectByLinkStatus"; +/// The statuses, as the command names them, with their menu items' and the dialog's labels. +pub const STATUSES: [(&str, &str, &str); 4] = [ + ("linked", "Select Linked", "Linked"), + ("embedded", "Select Embedded", "Embedded"), + ("missing", "Select Missing", "Missing"), + ("modified", "Select Modified", "Modified"), +]; +/// The dialog's Show choices: (value, label). +const SHOW: [(&str, &str); 5] = [("all", "All"), ("linked", "Linked"), ("embedded", "Embedded"), ("missing", "Missing"), ("modified", "Modified")]; +/// How often the statuses are looked at again while the document stays as it is (s): files +/// change on disk without the document knowing. +const RECHECK: f64 = 2.0; +/// The least time between two looks while the document keeps changing (s). +const SETTLE: f64 = 0.5; +const ROW_H: f32 = 22.0; +const LIST_H: f32 = 240.0; +const STATUS_W: f32 = 80.0; +const FORMAT_W: f32 = 70.0; + +/// The menu items: one per status, then the dialog. +pub fn menu() -> Vec { + let mut v: Vec = STATUSES.iter().map(|&(status, label, _)| Item::Cmd(label, COMMAND, json!({ "status": status }))).collect(); + v.push(Item::Cmd("Select by Link Status…", COMMAND, Value::Null)); + v +} + +/// Which statuses ([`STATUSES`] order) the active document's images and placed documents have, +/// for the document and revision it was read for and when. +#[derive(Clone, Debug, Default)] +pub struct Present { + key: Option<(u64, u64)>, + at: f64, + pub has: [bool; 4], +} + +/// The statuses the `links.list` result `list` holds, in [`STATUSES`] order. +pub(crate) fn statuses_in(list: &Value) -> [bool; 4] { + let rows = list["links"].as_array().map_or(&[][..], Vec::as_slice); + STATUSES.map(|(status, ..)| rows.iter().any(|r| command_status(r) == status)) +} + +/// The status of a `links.list` row as the command names it (`ok` is `linked`). +fn command_status(row: &Value) -> &str { + match row["status"].as_str().unwrap_or_default() { + "ok" => "linked", + s => s, + } +} + +/// Keep [`Present`] up to date: at once when another document comes to the front, at most every +/// [`SETTLE`] seconds while its revision changes (a drag changes it every frame, and each look +/// asks the file system about every linked file), and every [`RECHECK`] seconds while it has +/// linked files. +pub fn refresh(app: &mut VectorcraftApp, now: f64) { + let key = app.session.active().map(|st| (st.uid, st.revision)); + let p = &app.ui.link_statuses; + let linked = p.has.iter().zip(STATUSES).any(|(on, (status, ..))| *on && status != "embedded"); + let other_document = p.key.map(|k| k.0) != key.map(|k| k.0); + let due = if p.key == key { linked && now - p.at >= RECHECK } else { other_document || now - p.at >= SETTLE }; + if !due { + return; + } + let has = if key.is_some() { app.session.execute("links.list", &json!({})).map(|l| statuses_in(&l)).unwrap_or_default() } else { [false; 4] }; + app.ui.link_statuses = Present { key, at: now, has }; +} + +/// Is menu item `status` (a [`STATUSES`] value) on: does an image or placed document have it? +pub(crate) fn enabled(app: &VectorcraftApp, p: &Value) -> bool { + let Some(status) = p.get("status").and_then(Value::as_str) else { return true }; + STATUSES.iter().position(|(s, ..)| *s == status).and_then(|i| app.ui.link_statuses.has.get(i).copied()).unwrap_or(false) +} + +/// A menu item's params with Shift held: add to the selection. +pub(crate) fn with_shift(mut p: Value) -> Value { + if let Some(o) = p.as_object_mut() + && o.contains_key("status") + { + o.insert("add".into(), json!(true)); + } + p +} + +/// Run the command; with `zoom` and something selected, Zoom to Selection; the status bar says +/// what was selected and what was left out. Without params (the menu's "Select by Link Status…") +/// it opens the dialog instead. +pub(crate) fn run(app: &mut VectorcraftApp, p: &Value) -> Result { + if p.as_object().is_none_or(serde_json::Map::is_empty) { + if app.session.active().is_none() { + return Err("no document".into()); + } + let fields = fields(app); + app.ui.dialog = Some(Dialog::new(KIND, fields)); + return Ok(json!({ "dialog": KIND })); + } + let r = app.session.execute(COMMAND, p).map_err(|e| e.to_string())?; + let count = r["count"].as_u64().unwrap_or(0); + if r["zoom"] == true && count > 0 { + crate::addons::arrange::zoom(app)?; + } + let statuses: Vec<&str> = match &p["status"] { + Value::Array(v) => v.iter().filter_map(Value::as_str).collect(), + v => v.as_str().into_iter().collect(), + }; + let reasons: Vec<&str> = r["skipped"].as_array().map_or(vec![], |v| v.iter().filter_map(|k| k["reason"].as_str()).collect()); + app.status(message(&statuses, count, &reasons)); + Ok(r) +} + +/// The status bar's line: `count` objects of `statuses` selected, and the `reasons` the others +/// were left out for (e.g. "Selected 9 missing images; 3 more are hidden or locked"). +pub(crate) fn message(statuses: &[&str], count: u64, reasons: &[&str]) -> String { + let n = count; + let selected = match statuses { + ["linked"] => crate::i18n::msg_n(n, "Selected {n} linked image", "Selected {n} linked images", &[]), + ["embedded"] => crate::i18n::msg_n(n, "Selected {n} embedded image", "Selected {n} embedded images", &[]), + ["missing"] => crate::i18n::msg_n(n, "Selected {n} missing image", "Selected {n} missing images", &[]), + ["modified"] => crate::i18n::msg_n(n, "Selected {n} modified image", "Selected {n} modified images", &[]), + _ => crate::i18n::msg_n(n, "Selected {n} image", "Selected {n} images", &[]), + }; + let Some(first) = reasons.first() else { return selected }; + // Hidden and locked go together; a template layer, or the isolated group, alone. + fn kind(r: &str) -> &str { + match r { + "hidden" | "locked" => "locked", + r => r, + } + } + let (one, other) = match kind(first) { + k if reasons.iter().any(|r| kind(r) != k) => ("{selected}; {n} more can't be selected now", "{selected}; {n} more can't be selected now"), + "template" => ("{selected}; {n} more is on a template layer", "{selected}; {n} more are on template layers"), + "isolation" => ("{selected}; {n} more is outside the isolated group", "{selected}; {n} more are outside the isolated group"), + _ => ("{selected}; {n} more is hidden or locked", "{selected}; {n} more are hidden or locked"), + }; + crate::i18n::msg_n(reasons.len() as u64, one, other, &[("selected", &selected)]) +} + +/// The dialog's fields to start with: every image and placed document (`__rows`, as `links.list` +/// lists them, read once), those selected chosen, All shown, Zoom to Selection on. +pub(crate) fn fields(app: &mut VectorcraftApp) -> Value { + let list = app.session.execute("links.list", &json!({})).unwrap_or_default(); + let rows: Vec = list["links"].as_array().cloned().unwrap_or_default(); + let selected: Vec = app.session.active().map(|st| st.selection.objects.iter().map(|id| id.0).collect()).unwrap_or_default(); + let chosen: Vec = rows.iter().filter_map(|r| r["id"].as_u64()).filter(|id| selected.contains(id)).collect(); + json!({ "__rows": rows, "ids": chosen, "show": "all", "zoom": true }) +} + +pub(crate) const SPEC: DialogSpec = DialogSpec { + heading: |_| tl!("Select by Link Status").to_string(), + body, + confirm, + ok: Some("Select"), + min_width: 420.0, + max_width: Some(480.0), + ok_enabled: Some(|_, d| !chosen(d).is_empty()), + ..DialogSpec::FORM +}; + +/// The ids chosen in dialog `d`. +pub(crate) fn chosen(d: &Dialog) -> Vec { + d.fields.get("ids").and_then(Value::as_array).into_iter().flatten().filter_map(Value::as_u64).collect() +} + +/// The rows of `rows` the name search `query` (ignoring case) and the Show choice `show` let through. +pub(crate) fn shown<'a>(rows: &'a [Value], query: &str, show: &str) -> Vec<&'a Value> { + let q = query.trim().to_lowercase(); + rows.iter() + .filter(|r| show == "all" || command_status(r) == show) + .filter(|r| q.is_empty() || r["name"].as_str().unwrap_or_default().to_lowercase().contains(&q)) + .collect() +} + +/// All (`on`) or None: choose every row `shown`, or none of them; rows the search or Show hides +/// keep their state. +pub(crate) fn choose_shown(chosen: &mut Vec, shown: &[u64], on: bool) { + if on { + for id in shown { + if !chosen.contains(id) { + chosen.push(*id); + } + } + } else { + chosen.retain(|id| !shown.contains(id)); + } +} + +/// Tick or untick row `id`. +pub(crate) fn toggle(chosen: &mut Vec, id: u64) { + match chosen.iter().position(|c| *c == id) { + Some(i) => { + chosen.remove(i); + } + None => chosen.push(id), + } +} + +/// A status's label in the list. +fn status_label(status: &str) -> &'static str { + STATUSES.iter().find(|(s, ..)| *s == status).map_or("", |&(.., label)| tl!(label)) +} + +fn body(_app: &mut VectorcraftApp, ui: &mut Ui, d: &mut Dialog) -> bool { + let t = Tokens::get(ui.ctx()); + let rows: Vec = d.fields.get("__rows").and_then(Value::as_array).cloned().unwrap_or_default(); + let query = widgets::search_field(ui, ui.scope_id().with("select-by-link-status-search"), tl!("Search images")); + d.fields.insert("__search".into(), json!(query)); + ui.add_space(4.0); + form::choice(ui, d, "show", tl!("Show:"), (60.0, 140.0), &SHOW); + let show = d.str("show"); + let visible = shown(&rows, &query, &show); + let mut picked = chosen(d); + ui.add_space(4.0); + egui::ScrollArea::vertical().max_height(LIST_H).auto_shrink([false, true]).show(ui, |ui| { + if visible.is_empty() { + ui.label(egui::RichText::new(tl!("No images match.")).color(t.text_dim)); + } + for r in &visible { + let Some(id) = r["id"].as_u64() else { continue }; + let name = r["name"].as_str().unwrap_or_default(); + let status = status_label(command_status(r)); + let format = r["format"].as_str().unwrap_or_default(); + ui.horizontal(|ui| { + ui.set_min_height(ROW_H); + let on = picked.contains(&id); + let name_w = (ui.available_width() - STATUS_W - FORMAT_W - 40.0).max(60.0); + let check = ui.scope(|ui| { + ui.set_width(name_w + 24.0); + widgets::check_name(ui, name, on) + }); + if check.inner { + toggle(&mut picked, id); + } + ui.add_sized(vec2(STATUS_W, ROW_H), egui::Label::new(egui::RichText::new(status).color(t.text_dim)).truncate()); + ui.add_sized(vec2(FORMAT_W, ROW_H), egui::Label::new(egui::RichText::new(format).color(t.text_dim)).truncate()); + }); + } + }); + ui.add_space(4.0); + let ids: Vec = visible.iter().filter_map(|r| r["id"].as_u64()).collect(); + ui.horizontal(|ui| { + if widgets::flat_button_fit(ui, "All", 50.0).clicked() { + choose_shown(&mut picked, &ids, true); + } + if widgets::flat_button_fit(ui, "None", 50.0).clicked() { + choose_shown(&mut picked, &ids, false); + } + let count = crate::i18n::fmt(tl!("Chosen: {n} of {total}"), &[("n", &picked.len().to_string()), ("total", &rows.len().to_string())]); + ui.label(egui::RichText::new(count).color(t.text_dim)); + }); + d.fields.insert("ids".into(), json!(picked)); + ui.add_space(6.0); + form::check(ui, d, "zoom", tl!("Zoom to Selection")); + false +} + +/// The command's params for dialog `d`: the rows chosen, whatever their status. +pub(crate) fn params(d: &Dialog) -> Value { + json!({ "status": STATUSES.map(|(s, ..)| s), "ids": chosen(d), "zoom": d.bool("zoom") }) +} + +/// Select: select the rows chosen (and zoom to them) and close. +fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { + app.ui.dialog = None; + app.run(COMMAND, params(d)) +} + +#[cfg(test)] +mod tests; diff --git a/crates/ui-egui/src/addons/select/link_status/tests.rs b/crates/ui-egui/src/addons/select/link_status/tests.rs new file mode 100644 index 000000000..22b07ccac --- /dev/null +++ b/crates/ui-egui/src/addons/select/link_status/tests.rs @@ -0,0 +1,199 @@ +use serde_json::{Value, json}; +use vectorcraft_engine::Session; + +use super::{COMMAND, KIND, choose_shown, message, refresh, shown, toggle}; +use crate::tests_labels::painted_text; +use crate::{VectorcraftApp, menus}; + +/// A fresh folder for one test (removed when dropped). +struct Folder(std::path::PathBuf); + +impl Folder { + fn new(name: &str) -> Self { + let dir = std::env::temp_dir().join(format!("vectorcraft-ui-select-links-{name}-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + Self(dir) + } + fn file(&self, name: &str, bytes: &[u8]) -> String { + let p = self.0.join(name); + std::fs::write(&p, bytes).unwrap(); + p.to_string_lossy().into_owned() + } +} + +impl Drop for Folder { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.0); + } +} + +fn png(w: u32, h: u32) -> Vec { + let mut out = vec![]; + image::RgbaImage::from_pixel(w, h, image::Rgba([200, 40, 40, 255])) + .write_to(&mut std::io::Cursor::new(&mut out), image::ImageFormat::Png) + .unwrap(); + out +} + +/// A document with `river.png` linked, `harbour.png` linked and then deleted (missing), and +/// `stamp.png` embedded → (app, river, harbour, stamp). +fn app(dir: &Folder) -> (VectorcraftApp, u64, u64, u64) { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + let place = |app: &mut VectorcraftApp, path: &str, at: [u32; 2], link: bool| { + app.run("file.place", json!({"path": path, "at": at, "link": link})).unwrap()["ids"][0].as_u64().unwrap() + }; + let (a, b, c) = (dir.file("river.png", &png(40, 30)), dir.file("harbour.png", &png(30, 20)), dir.file("stamp.png", &png(20, 20))); + let river = place(&mut app, &a, [100, 100], true); + let harbour = place(&mut app, &b, [300, 100], true); + let stamp = place(&mut app, &c, [200, 220], false); + std::fs::remove_file(&b).unwrap(); + app.run("select.none", json!({})).unwrap(); + (app, river, harbour, stamp) +} + +fn selected(app: &VectorcraftApp) -> Vec { + let mut ids: Vec = app.session.active().unwrap().selection.objects.iter().map(|id| id.0).collect(); + ids.sort_unstable(); + ids +} + +#[test] +fn the_message_says_what_was_selected_and_what_was_left_out() { + assert_eq!(message(&["missing"], 9, &[]), "Selected 9 missing images"); + assert_eq!(message(&["linked"], 1, &[]), "Selected 1 linked image"); + assert_eq!(message(&["embedded", "modified"], 2, &[]), "Selected 2 images"); + assert_eq!(message(&["missing"], 9, &["hidden", "locked", "hidden"]), "Selected 9 missing images; 3 more are hidden or locked"); + assert_eq!(message(&["modified"], 0, &["template"]), "Selected 0 modified images; 1 more is on a template layer"); + assert_eq!(message(&["embedded"], 2, &["isolation", "isolation"]), "Selected 2 embedded images; 2 more are outside the isolated group"); + assert_eq!(message(&["linked"], 1, &["isolation", "locked"]), "Selected 1 linked image; 2 more can't be selected now"); +} + +#[test] +fn the_list_filters_by_name_and_status_and_all_and_none_act_on_the_rows_shown() { + let rows = vec![ + json!({"id": 1, "name": "River.png", "status": "ok"}), + json!({"id": 2, "name": "harbour.png", "status": "missing"}), + json!({"id": 3, "name": "Stamp.png", "status": "embedded"}), + json!({"id": 4, "name": "river-2.tif", "status": "modified"}), + ]; + let ids = |v: Vec<&Value>| v.iter().map(|r| r["id"].as_u64().unwrap()).collect::>(); + assert_eq!(ids(shown(&rows, "", "all")), vec![1, 2, 3, 4]); + assert_eq!(ids(shown(&rows, " RIVER ", "all")), vec![1, 4]); + assert_eq!(ids(shown(&rows, "river", "linked")), vec![1], "ok is Linked"); + assert_eq!(ids(shown(&rows, "", "missing")), vec![2]); + assert_eq!(ids(shown(&rows, "zzz", "all")), Vec::::new()); + // Row 3 is chosen but hidden: neither All nor None touches it. + let mut c = vec![3]; + choose_shown(&mut c, &[1, 4], true); + assert_eq!(c, vec![3, 1, 4]); + choose_shown(&mut c, &[1, 4], false); + assert_eq!(c, vec![3]); + toggle(&mut c, 2); + toggle(&mut c, 3); + assert_eq!(c, vec![2]); +} + +#[test] +fn menu_items_are_on_for_the_statuses_present_and_shift_adds() { + let dir = Folder::new("menu"); + let (mut app, river, harbour, stamp) = app(&dir); + refresh(&mut app, 0.0); + let entries = menus::menu_entries(&app); + let item = |label: &str| entries.iter().find(|e| e.path == ["Object", "Plug-ins", "Selection Tools"] && e.label == label).unwrap(); + for (label, on) in + [("Select Linked", true), ("Select Embedded", true), ("Select Missing", true), ("Select Modified", false), ("Select by Link Status…", true)] + { + assert_eq!(item(label).enabled, on, "{label}"); + } + let run = |app: &mut VectorcraftApp, label: &str, shift: bool| { + let e = item(label); + let target = menus::click_target(&e.label, e.command.as_deref().unwrap(), &e.params); + let held = if shift { egui::Modifiers::SHIFT } else { egui::Modifiers::NONE }; + let (cmd, p) = menus::with_held_keys(target, held); + menus::invoke(app, &cmd, p); + }; + run(&mut app, "Select Missing", false); + assert_eq!(selected(&app), vec![harbour]); + assert_eq!(app.ui.status, "Selected 1 missing image"); + run(&mut app, "Select Embedded", true); + assert_eq!(selected(&app), vec![harbour, stamp], "Shift adds"); + run(&mut app, "Select Linked", false); + assert_eq!(selected(&app), vec![river], "without Shift it replaces"); + // A hidden image is left out, and the status bar says so. + app.run("object.setProps", json!({"ids": [river], "visible": false})).unwrap(); + run(&mut app, "Select Linked", false); + assert_eq!(selected(&app), Vec::::new()); + assert_eq!(app.ui.status, "Selected 0 linked images; 1 more is hidden or locked"); + // The document changes: the items follow on the next refresh. + app.run("select.set", json!({"ids": [harbour]})).unwrap(); + app.run("edit.clear", json!({})).unwrap(); + refresh(&mut app, 1.0); + let entries = menus::menu_entries(&app); + let on = |label: &str| entries.iter().find(|e| e.path == ["Object", "Plug-ins", "Selection Tools"] && e.label == label).unwrap().enabled; + assert!(!on("Select Missing")); + assert!(on("Select Embedded")); +} + +#[test] +fn the_links_panel_menu_lists_the_select_items() { + let dir = Folder::new("flyout"); + let (mut app, ..) = app(&dir); + refresh(&mut app, 0.0); + let shown = painted_text(&mut app, crate::panels::links::menu); + for s in ["Select Linked", "Select Embedded", "Select Missing", "Select Modified"] { + assert!(shown.contains(s), "{s} in {shown}"); + } +} + +#[test] +fn the_dialog_lists_filters_and_selects_with_zoom() { + let dir = Folder::new("dialog"); + let (mut app, river, harbour, stamp) = app(&dir); + app.canvas_rect = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(560.0, 460.0))); + let zoom_before = app.view().unwrap().zoom; + menus::invoke(&mut app, COMMAND, Value::Null); + let d = app.ui.dialog.as_ref().unwrap(); + assert_eq!((d.kind.as_str(), &d.fields["zoom"], &d.fields["show"]), (KIND, &json!(true), &json!("all"))); + let shown = painted_text(&mut app, |app, ui| crate::dialogs::show(app, ui.ctx())); + for s in [ + "Select by Link Status", + "river.png", + "harbour.png", + "stamp.png", + "Linked", + "Missing", + "Embedded", + "PNG", + "Zoom to Selection", + "Chosen: 0 of 3", + "All", + "None", + "Select", + ] { + assert!(shown.contains(s), "{s} in {shown}"); + } + assert!(!(super::SPEC.ok_enabled.unwrap())(&app, app.ui.dialog.as_ref().unwrap()), "Select is off with nothing chosen"); + // Show: Missing lists only the missing image. + app.ui.dialog.as_mut().unwrap().fields.insert("show".into(), json!("missing")); + let shown = painted_text(&mut app, |app, ui| crate::dialogs::show(app, ui.ctx())); + assert!(shown.contains("harbour.png") && !shown.contains("river.png") && !shown.contains("stamp.png"), "{shown}"); + // Choose two and Select: they are selected and the view zooms to them. + let d = app.ui.dialog.as_mut().unwrap(); + d.fields.insert("ids".into(), json!([river, stamp])); + crate::dialogs::confirm(&mut app).unwrap(); + assert!(app.ui.dialog.is_none()); + assert_eq!(selected(&app), vec![river, stamp]); + assert_eq!(app.ui.status, "Selected 2 images"); + assert_ne!(app.view().unwrap().zoom, zoom_before, "zoomed to the selection"); + // Without Zoom to Selection the view stays. + menus::invoke(&mut app, COMMAND, Value::Null); + let zoom = app.view().unwrap().zoom; + let d = app.ui.dialog.as_mut().unwrap(); + d.fields.insert("ids".into(), json!([harbour])); + d.fields.insert("zoom".into(), json!(false)); + crate::dialogs::confirm(&mut app).unwrap(); + assert_eq!(selected(&app), vec![harbour]); + assert_eq!(app.view().unwrap().zoom, zoom); +} diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index eb9c3526e..7a8e99409 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -314,6 +314,7 @@ impl VectorcraftApp { placing::poll(self); #[cfg(not(target_arch = "wasm32"))] self.tick_link_updates(ctx, now); + crate::addons::select::link_status::refresh(self, now); if !self.background.jobs.is_empty() || !self.opening.pending.is_empty() || !self.plugin_runs.running.is_empty() diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 2a90425c1..c0f900f67 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -422,6 +422,7 @@ registry! { SwatchLegend: [crate::addons::color::legend::KIND] => crate::addons::color::legend::SPEC, TextTweaker: [crate::addons::typetools::KIND] => crate::addons::typetools::SPEC, SelectByArtboards: [crate::addons::select::KIND] => crate::addons::select::SPEC, + SelectByLinkStatus: [crate::addons::select::link_status::KIND] => crate::addons::select::link_status::SPEC, ArrangeTools: [crate::addons::arrange::GRID, crate::addons::arrange::REPLACE, crate::addons::arrange::SIZE, crate::addons::arrange::OPACITY, crate::addons::arrange::SORT] => crate::addons::arrange::SPEC, ShortcutImport: [shortcut_import::KIND] => DialogSpec::window(shortcut_import::show, shortcut_import::confirm), } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 07c4f90d9..5c297aeb2 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -6215,6 +6215,25 @@ breakText Glyphs Глифи Minimum angle Минимален ъгъл Maximum angle Максимален ъгъл +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Избиране на свързаните + Select Embedded Избиране на вградените + Select Missing Избиране на липсващите + Select Modified Избиране на модифицираните + Select by Link Status Избиране по състояние на връзката + Select by Link Status… Избиране по състояние на връзката… + Search images Търсене на изображения + No images match. Няма съвпадащи изображения. +@msgplural Selected {n} linked image|Selected {n} linked images Избрано е {n} свързано изображение|Избрани са {n} свързани изображения +@msgplural Selected {n} embedded image|Selected {n} embedded images Избрано е {n} вградено изображение|Избрани са {n} вградени изображения +@msgplural Selected {n} missing image|Selected {n} missing images Избрано е {n} липсващо изображение|Избрани са {n} липсващи изображения +@msgplural Selected {n} modified image|Selected {n} modified images Избрано е {n} модифицирано изображение|Избрани са {n} модифицирани изображения +@msgplural Selected {n} image|Selected {n} images Избрано е {n} изображение|Избрани са {n} изображения +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; още {n} е скрито или заключено|{selected}; още {n} са скрити или заключени +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; още {n} е в шаблонен слой|{selected}; още {n} са в шаблонни слоеве +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; още {n} е извън изолираната група|{selected}; още {n} са извън изолираната група +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; още {n} не може да бъде избрано сега|{selected}; още {n} не могат да бъдат избрани сега + # Compound Shapes add-on Compound Shapes Съставни фигури Shape Mode Buttons Make Compound Shapes Бутоните за режими на фигури създават съставни фигури diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index d007b6829..12b748971 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -6250,6 +6250,25 @@ shape Open Otvoren Minimum angle Najmanji ugao Maximum angle Najveći ugao +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Odaberi povezane + Select Embedded Odaberi ugrađene + Select Missing Odaberi nedostajuće + Select Modified Odaberi izmijenjene + Select by Link Status Odabir po statusu veze + Select by Link Status… Odabir po statusu veze… + Search images Pretraži slike + No images match. Nijedna slika ne odgovara. +@msgplural Selected {n} linked image|Selected {n} linked images Odabrana je {n} povezana slika|Odabrane su {n} povezane slike|Odabrano je {n} povezanih slika +@msgplural Selected {n} embedded image|Selected {n} embedded images Odabrana je {n} ugrađena slika|Odabrane su {n} ugrađene slike|Odabrano je {n} ugrađenih slika +@msgplural Selected {n} missing image|Selected {n} missing images Odabrana je {n} nedostajuća slika|Odabrane su {n} nedostajuće slike|Odabrano je {n} nedostajućih slika +@msgplural Selected {n} modified image|Selected {n} modified images Odabrana je {n} izmijenjena slika|Odabrane su {n} izmijenjene slike|Odabrano je {n} izmijenjenih slika +@msgplural Selected {n} image|Selected {n} images Odabrana je {n} slika|Odabrane su {n} slike|Odabrano je {n} slika +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; još {n} je skrivena ili zaključana|{selected}; još {n} su skrivene ili zaključane|{selected}; još {n} je skriveno ili zaključano +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; još {n} je na sloju predloška|{selected}; još {n} su na slojevima predloška|{selected}; još {n} je na slojevima predloška +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; još {n} je izvan izolirane grupe|{selected}; još {n} su izvan izolirane grupe|{selected}; još {n} je izvan izolirane grupe +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; još {n} se sada ne može odabrati|{selected}; još {n} se sada ne mogu odabrati|{selected}; još {n} se sada ne može odabrati + # Compound Shapes add-on Compound Shapes Složeni oblici Shape Mode Buttons Make Compound Shapes Tasteri načina oblika prave složene oblike diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 34988b565..d68f2a5bc 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -6351,6 +6351,25 @@ breakText Glyphs Glifs Minimum angle Angle mínim Maximum angle Angle màxim +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Selecciona les enllaçades + Select Embedded Selecciona les incrustades + Select Missing Selecciona les que falten + Select Modified Selecciona les modificades + Select by Link Status Selecciona per estat de l’enllaç + Select by Link Status… Selecciona per estat de l’enllaç… + Search images Cerca imatges + No images match. Cap imatge no coincideix. +@msgplural Selected {n} linked image|Selected {n} linked images S’ha seleccionat {n} imatge enllaçada|S’han seleccionat {n} imatges enllaçades +@msgplural Selected {n} embedded image|Selected {n} embedded images S’ha seleccionat {n} imatge incrustada|S’han seleccionat {n} imatges incrustades +@msgplural Selected {n} missing image|Selected {n} missing images S’ha seleccionat {n} imatge que falta|S’han seleccionat {n} imatges que falten +@msgplural Selected {n} modified image|Selected {n} modified images S’ha seleccionat {n} imatge modificada|S’han seleccionat {n} imatges modificades +@msgplural Selected {n} image|Selected {n} images S’ha seleccionat {n} imatge|S’han seleccionat {n} imatges +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; {n} més està oculta o bloquejada|{selected}; {n} més estan ocultes o bloquejades +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; {n} més és en una capa de plantilla|{selected}; {n} més són en capes de plantilla +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; {n} més és fora del grup aïllat|{selected}; {n} més són fora del grup aïllat +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; {n} més no es pot seleccionar ara|{selected}; {n} més no es poden seleccionar ara + # Compound Shapes add-on Compound Shapes Formes compostes Shape Mode Buttons Make Compound Shapes Els botons de mode de forma creen formes compostes diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 57c3092b0..10a96a0e8 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -6365,6 +6365,25 @@ breakText Glyphs Glyfy Minimum angle Minimální úhel Maximum angle Maximální úhel +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Vybrat propojené + Select Embedded Vybrat vložené + Select Missing Vybrat chybějící + Select Modified Vybrat upravené + Select by Link Status Vybrat podle stavu propojení + Select by Link Status… Vybrat podle stavu propojení… + Search images Hledat obrazy + No images match. Žádný obraz neodpovídá. +@msgplural Selected {n} linked image|Selected {n} linked images Vybrán {n} propojený obraz|Vybrány {n} propojené obrazy|Vybráno {n} propojených obrazů +@msgplural Selected {n} embedded image|Selected {n} embedded images Vybrán {n} vložený obraz|Vybrány {n} vložené obrazy|Vybráno {n} vložených obrazů +@msgplural Selected {n} missing image|Selected {n} missing images Vybrán {n} chybějící obraz|Vybrány {n} chybějící obrazy|Vybráno {n} chybějících obrazů +@msgplural Selected {n} modified image|Selected {n} modified images Vybrán {n} upravený obraz|Vybrány {n} upravené obrazy|Vybráno {n} upravených obrazů +@msgplural Selected {n} image|Selected {n} images Vybrán {n} obraz|Vybrány {n} obrazy|Vybráno {n} obrazů +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; další {n} je skrytý nebo zamčený|{selected}; další {n} jsou skryté nebo zamčené|{selected}; dalších {n} je skrytých nebo zamčených +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; další {n} je na vrstvě šablony|{selected}; další {n} jsou na vrstvách šablony|{selected}; dalších {n} je na vrstvách šablony +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; další {n} je mimo izolovanou skupinu|{selected}; další {n} jsou mimo izolovanou skupinu|{selected}; dalších {n} je mimo izolovanou skupinu +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; další {n} teď nelze vybrat|{selected}; další {n} teď nelze vybrat|{selected}; dalších {n} teď nelze vybrat + # Compound Shapes add-on Compound Shapes Složené tvary Shape Mode Buttons Make Compound Shapes Tlačítka režimů tvarů vytvářejí složené tvary diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index b5980d572..ae8069ffa 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -6231,6 +6231,25 @@ breakText Glyphs Glyffer Minimum angle Mindste vinkel Maximum angle Største vinkel +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Markér linkede + Select Embedded Markér integrerede + Select Missing Markér manglende + Select Modified Markér ændrede + Select by Link Status Markér efter linkstatus + Select by Link Status… Markér efter linkstatus… + Search images Søg i billeder + No images match. Ingen billeder passer. +@msgplural Selected {n} linked image|Selected {n} linked images Markerede {n} linket billede|Markerede {n} linkede billeder +@msgplural Selected {n} embedded image|Selected {n} embedded images Markerede {n} integreret billede|Markerede {n} integrerede billeder +@msgplural Selected {n} missing image|Selected {n} missing images Markerede {n} manglende billede|Markerede {n} manglende billeder +@msgplural Selected {n} modified image|Selected {n} modified images Markerede {n} ændret billede|Markerede {n} ændrede billeder +@msgplural Selected {n} image|Selected {n} images Markerede {n} billede|Markerede {n} billeder +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; {n} mere er skjult eller låst|{selected}; {n} mere er skjult eller låst +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; {n} mere er på et skabelonlag|{selected}; {n} mere er på skabelonlag +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; {n} mere er uden for den isolerede gruppe|{selected}; {n} mere er uden for den isolerede gruppe +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; {n} mere kan ikke markeres nu|{selected}; {n} mere kan ikke markeres nu + # Compound Shapes add-on Compound Shapes Sammensatte figurer Shape Mode Buttons Make Compound Shapes Knapper til formtilstande opretter sammensatte figurer diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 5e38f90f5..436940908 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5951,6 +5951,25 @@ breakText Glyphs Glyphen Minimum angle Minimaler Winkel Maximum angle Maximaler Winkel +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Verknüpfte auswählen + Select Embedded Eingebettete auswählen + Select Missing Fehlende auswählen + Select Modified Geänderte auswählen + Select by Link Status Nach Verknüpfungsstatus auswählen + Select by Link Status… Nach Verknüpfungsstatus auswählen… + Search images Bilder suchen + No images match. Kein Bild passt. +@msgplural Selected {n} linked image|Selected {n} linked images {n} verknüpftes Bild ausgewählt|{n} verknüpfte Bilder ausgewählt +@msgplural Selected {n} embedded image|Selected {n} embedded images {n} eingebettetes Bild ausgewählt|{n} eingebettete Bilder ausgewählt +@msgplural Selected {n} missing image|Selected {n} missing images {n} fehlendes Bild ausgewählt|{n} fehlende Bilder ausgewählt +@msgplural Selected {n} modified image|Selected {n} modified images {n} geändertes Bild ausgewählt|{n} geänderte Bilder ausgewählt +@msgplural Selected {n} image|Selected {n} images {n} Bild ausgewählt|{n} Bilder ausgewählt +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; {n} weiteres ist ausgeblendet oder gesperrt|{selected}; {n} weitere sind ausgeblendet oder gesperrt +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; {n} weiteres liegt auf einer Vorlagenebene|{selected}; {n} weitere liegen auf Vorlagenebenen +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; {n} weiteres liegt außerhalb der isolierten Gruppe|{selected}; {n} weitere liegen außerhalb der isolierten Gruppe +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; {n} weiteres kann gerade nicht ausgewählt werden|{selected}; {n} weitere können gerade nicht ausgewählt werden + # Compound Shapes add-on Compound Shapes Zusammengesetzte Formen Shape Mode Buttons Make Compound Shapes Formmodus-Schaltflächen erstellen zusammengesetzte Formen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 84232bda2..9dd19335d 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -6317,6 +6317,25 @@ breakText Glyphs Γλύφοι Minimum angle Ελάχιστη γωνία Maximum angle Μέγιστη γωνία +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Επιλογή συνδεδεμένων + Select Embedded Επιλογή ενσωματωμένων + Select Missing Επιλογή όσων λείπουν + Select Modified Επιλογή τροποποιημένων + Select by Link Status Επιλογή κατά κατάσταση σύνδεσης + Select by Link Status… Επιλογή κατά κατάσταση σύνδεσης… + Search images Αναζήτηση εικόνων + No images match. Καμία εικόνα δεν ταιριάζει. +@msgplural Selected {n} linked image|Selected {n} linked images Επιλέχθηκε {n} συνδεδεμένη εικόνα|Επιλέχθηκαν {n} συνδεδεμένες εικόνες +@msgplural Selected {n} embedded image|Selected {n} embedded images Επιλέχθηκε {n} ενσωματωμένη εικόνα|Επιλέχθηκαν {n} ενσωματωμένες εικόνες +@msgplural Selected {n} missing image|Selected {n} missing images Επιλέχθηκε {n} εικόνα που λείπει|Επιλέχθηκαν {n} εικόνες που λείπουν +@msgplural Selected {n} modified image|Selected {n} modified images Επιλέχθηκε {n} τροποποιημένη εικόνα|Επιλέχθηκαν {n} τροποποιημένες εικόνες +@msgplural Selected {n} image|Selected {n} images Επιλέχθηκε {n} εικόνα|Επιλέχθηκαν {n} εικόνες +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}· {n} ακόμη είναι κρυφή ή κλειδωμένη|{selected}· {n} ακόμη είναι κρυφές ή κλειδωμένες +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}· {n} ακόμη είναι σε επίπεδο προτύπου|{selected}· {n} ακόμη είναι σε επίπεδα προτύπου +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}· {n} ακόμη είναι εκτός της απομονωμένης ομάδας|{selected}· {n} ακόμη είναι εκτός της απομονωμένης ομάδας +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}· {n} ακόμη δεν μπορεί να επιλεγεί τώρα|{selected}· {n} ακόμη δεν μπορούν να επιλεγούν τώρα + # Compound Shapes add-on Compound Shapes Σύνθετα σχήματα Shape Mode Buttons Make Compound Shapes Τα κουμπιά λειτουργιών σχημάτων δημιουργούν σύνθετα σχήματα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index c09b51167..46ed40b7d 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -6307,6 +6307,25 @@ breakText Glyphs Glifos Minimum angle Ángulo mínimo Maximum angle Ángulo máximo +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Seleccionar vinculadas + Select Embedded Seleccionar incrustadas + Select Missing Seleccionar ausentes + Select Modified Seleccionar modificadas + Select by Link Status Seleccionar por estado de vínculo + Select by Link Status… Seleccionar por estado de vínculo… + Search images Buscar imágenes + No images match. Ninguna imagen coincide. +@msgplural Selected {n} linked image|Selected {n} linked images Se seleccionó {n} imagen vinculada|Se seleccionaron {n} imágenes vinculadas +@msgplural Selected {n} embedded image|Selected {n} embedded images Se seleccionó {n} imagen incrustada|Se seleccionaron {n} imágenes incrustadas +@msgplural Selected {n} missing image|Selected {n} missing images Se seleccionó {n} imagen ausente|Se seleccionaron {n} imágenes ausentes +@msgplural Selected {n} modified image|Selected {n} modified images Se seleccionó {n} imagen modificada|Se seleccionaron {n} imágenes modificadas +@msgplural Selected {n} image|Selected {n} images Se seleccionó {n} imagen|Se seleccionaron {n} imágenes +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; {n} más está oculta o bloqueada|{selected}; {n} más están ocultas o bloqueadas +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; {n} más está en una capa de plantilla|{selected}; {n} más están en capas de plantilla +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; {n} más está fuera del grupo aislado|{selected}; {n} más están fuera del grupo aislado +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; {n} más no se puede seleccionar ahora|{selected}; {n} más no se pueden seleccionar ahora + # Compound Shapes add-on Compound Shapes Formas compuestas Shape Mode Buttons Make Compound Shapes Los botones de modo de forma crean formas compuestas diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 909240b3d..16860a45f 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6400,6 +6400,25 @@ breakText Glyphs Merkit Minimum angle Pienin kulma Maximum angle Suurin kulma +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Valitse linkitetyt + Select Embedded Valitse upotetut + Select Missing Valitse puuttuvat + Select Modified Valitse muokatut + Select by Link Status Valitse linkin tilan mukaan + Select by Link Status… Valitse linkin tilan mukaan… + Search images Hae kuvia + No images match. Mikään kuva ei vastaa hakua. +@msgplural Selected {n} linked image|Selected {n} linked images Valittiin {n} linkitetty kuva|Valittiin {n} linkitettyä kuvaa +@msgplural Selected {n} embedded image|Selected {n} embedded images Valittiin {n} upotettu kuva|Valittiin {n} upotettua kuvaa +@msgplural Selected {n} missing image|Selected {n} missing images Valittiin {n} puuttuva kuva|Valittiin {n} puuttuvaa kuvaa +@msgplural Selected {n} modified image|Selected {n} modified images Valittiin {n} muokattu kuva|Valittiin {n} muokattua kuvaa +@msgplural Selected {n} image|Selected {n} images Valittiin {n} kuva|Valittiin {n} kuvaa +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; {n} muu on piilotettu tai lukittu|{selected}; {n} muuta on piilotettu tai lukittu +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; {n} muu on mallitasolla|{selected}; {n} muuta on mallitasoilla +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; {n} muu on eristetyn ryhmän ulkopuolella|{selected}; {n} muuta on eristetyn ryhmän ulkopuolella +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; {n} muuta ei voi valita nyt|{selected}; {n} muuta ei voi valita nyt + # Compound Shapes add-on Compound Shapes Yhdistelmämuodot Shape Mode Buttons Make Compound Shapes Muototilapainikkeet luovat yhdistelmämuotoja diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 6738ec8df..c028d24c8 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6360,6 +6360,25 @@ breakText Glyphs Glyphes Minimum angle Angle minimal Maximum angle Angle maximal +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Sélectionner les images liées + Select Embedded Sélectionner les images incorporées + Select Missing Sélectionner les images manquantes + Select Modified Sélectionner les images modifiées + Select by Link Status Sélectionner par état de lien + Select by Link Status… Sélectionner par état de lien… + Search images Rechercher des images + No images match. Aucune image ne correspond. +@msgplural Selected {n} linked image|Selected {n} linked images {n} image liée sélectionnée|{n} images liées sélectionnées +@msgplural Selected {n} embedded image|Selected {n} embedded images {n} image incorporée sélectionnée|{n} images incorporées sélectionnées +@msgplural Selected {n} missing image|Selected {n} missing images {n} image manquante sélectionnée|{n} images manquantes sélectionnées +@msgplural Selected {n} modified image|Selected {n} modified images {n} image modifiée sélectionnée|{n} images modifiées sélectionnées +@msgplural Selected {n} image|Selected {n} images {n} image sélectionnée|{n} images sélectionnées +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected} ; {n} autre est masquée ou verrouillée|{selected} ; {n} autres sont masquées ou verrouillées +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected} ; {n} autre est sur un calque modèle|{selected} ; {n} autres sont sur des calques modèles +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected} ; {n} autre est hors du groupe isolé|{selected} ; {n} autres sont hors du groupe isolé +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected} ; {n} autre ne peut pas être sélectionnée pour l’instant|{selected} ; {n} autres ne peuvent pas être sélectionnées pour l’instant + # Compound Shapes add-on Compound Shapes Formes composées Shape Mode Buttons Make Compound Shapes Les boutons de mode de forme créent des formes composées diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 491c89fae..3eb35419c 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -6349,6 +6349,25 @@ breakText Glyphs Glifák Minimum angle Legkisebb szög Maximum angle Legnagyobb szög +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Csatoltak kijelölése + Select Embedded Beágyazottak kijelölése + Select Missing Hiányzók kijelölése + Select Modified Módosítottak kijelölése + Select by Link Status Kijelölés csatolási állapot szerint + Select by Link Status… Kijelölés csatolási állapot szerint… + Search images Képek keresése + No images match. Nincs egyező kép. +@msgplural Selected {n} linked image|Selected {n} linked images Kijelölt csatolt képek: {n} +@msgplural Selected {n} embedded image|Selected {n} embedded images Kijelölt beágyazott képek: {n} +@msgplural Selected {n} missing image|Selected {n} missing images Kijelölt hiányzó képek: {n} +@msgplural Selected {n} modified image|Selected {n} modified images Kijelölt módosított képek: {n} +@msgplural Selected {n} image|Selected {n} images Kijelölt képek: {n} +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; további rejtett vagy zárolt: {n} +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; további sablonrétegen: {n} +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; további az elkülönített csoporton kívül: {n} +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; további most nem jelölhető ki: {n} + # Compound Shapes add-on Compound Shapes Összetett alakzatok Shape Mode Buttons Make Compound Shapes Az alakzatmód-gombok összetett alakzatokat hoznak létre diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index ffb0cbf56..33c329600 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -6231,6 +6231,25 @@ breakText Glyphs Glif Minimum angle Sudut minimum Maximum angle Sudut maksimum +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Pilih yang Tertaut + Select Embedded Pilih yang Tersemat + Select Missing Pilih yang Hilang + Select Modified Pilih yang Diubah + Select by Link Status Pilih menurut Status Tautan + Select by Link Status… Pilih menurut Status Tautan… + Search images Cari gambar + No images match. Tidak ada gambar yang cocok. +@msgplural Selected {n} linked image|Selected {n} linked images Telah memilih {n} gambar tertaut +@msgplural Selected {n} embedded image|Selected {n} embedded images Telah memilih {n} gambar tersemat +@msgplural Selected {n} missing image|Selected {n} missing images Telah memilih {n} gambar yang hilang +@msgplural Selected {n} modified image|Selected {n} modified images Telah memilih {n} gambar yang diubah +@msgplural Selected {n} image|Selected {n} images Telah memilih {n} gambar +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; {n} lainnya tersembunyi atau terkunci +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; {n} lainnya berada di layer templat +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; {n} lainnya berada di luar grup yang diisolasi +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; {n} lainnya tidak dapat dipilih sekarang + # Compound Shapes add-on Compound Shapes Bentuk majemuk Shape Mode Buttons Make Compound Shapes Tombol mode bentuk membuat bentuk majemuk diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index cfd99e62b..e59c310d8 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5985,6 +5985,25 @@ breakText Glyphs Glifi Minimum angle Angolo minimo Maximum angle Angolo massimo +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Seleziona collegate + Select Embedded Seleziona incorporate + Select Missing Seleziona mancanti + Select Modified Seleziona modificate + Select by Link Status Seleziona per stato del collegamento + Select by Link Status… Seleziona per stato del collegamento… + Search images Cerca immagini + No images match. Nessuna immagine corrisponde. +@msgplural Selected {n} linked image|Selected {n} linked images Selezionata {n} immagine collegata|Selezionate {n} immagini collegate +@msgplural Selected {n} embedded image|Selected {n} embedded images Selezionata {n} immagine incorporata|Selezionate {n} immagini incorporate +@msgplural Selected {n} missing image|Selected {n} missing images Selezionata {n} immagine mancante|Selezionate {n} immagini mancanti +@msgplural Selected {n} modified image|Selected {n} modified images Selezionata {n} immagine modificata|Selezionate {n} immagini modificate +@msgplural Selected {n} image|Selected {n} images Selezionata {n} immagine|Selezionate {n} immagini +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; altra {n} è nascosta o bloccata|{selected}; altre {n} sono nascoste o bloccate +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; altra {n} è su un livello modello|{selected}; altre {n} sono su livelli modello +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; altra {n} è fuori dal gruppo isolato|{selected}; altre {n} sono fuori dal gruppo isolato +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; altra {n} non può essere selezionata ora|{selected}; altre {n} non possono essere selezionate ora + # Compound Shapes add-on Compound Shapes Forme composte Shape Mode Buttons Make Compound Shapes I pulsanti dei metodi forma creano forme composte diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 5da2097f2..c0f05308c 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -6050,6 +6050,25 @@ breakText Glyphs 字形 Minimum angle 最小角度 Maximum angle 最大角度 +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked リンク済みを選択 + Select Embedded 埋め込みを選択 + Select Missing 見つからない画像を選択 + Select Modified 変更された画像を選択 + Select by Link Status リンクの状態で選択 + Select by Link Status… リンクの状態で選択… + Search images 画像を検索 + No images match. 一致する画像はありません。 +@msgplural Selected {n} linked image|Selected {n} linked images リンク済みの画像を {n} 個選択しました +@msgplural Selected {n} embedded image|Selected {n} embedded images 埋め込み画像を {n} 個選択しました +@msgplural Selected {n} missing image|Selected {n} missing images 見つからない画像を {n} 個選択しました +@msgplural Selected {n} modified image|Selected {n} modified images 変更された画像を {n} 個選択しました +@msgplural Selected {n} image|Selected {n} images 画像を {n} 個選択しました +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}。ほかに {n} 個が非表示またはロックされています +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}。ほかに {n} 個がテンプレートレイヤー上にあります +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}。ほかに {n} 個が分離したグループの外にあります +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}。ほかに {n} 個は今は選択できません + # Compound Shapes add-on Compound Shapes 複合シェイプ Shape Mode Buttons Make Compound Shapes シェイプモードボタンで複合シェイプを作成 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index b9f4a70a0..82127af21 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -6376,6 +6376,25 @@ font-script Japanese იაპონური Minimum angle მინიმალური კუთხე Maximum angle მაქსიმალური კუთხე +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked დაკავშირებულების არჩევა + Select Embedded ჩაშენებულების არჩევა + Select Missing დაკარგულების არჩევა + Select Modified შეცვლილების არჩევა + Select by Link Status კავშირის მდგომარეობით არჩევა + Select by Link Status… კავშირის მდგომარეობით არჩევა… + Search images გამოსახულებების ძებნა + No images match. შესაბამისი გამოსახულება არ არის. +@msgplural Selected {n} linked image|Selected {n} linked images არჩეულია დაკავშირებული გამოსახულებები: {n} +@msgplural Selected {n} embedded image|Selected {n} embedded images არჩეულია ჩაშენებული გამოსახულებები: {n} +@msgplural Selected {n} missing image|Selected {n} missing images არჩეულია დაკარგული გამოსახულებები: {n} +@msgplural Selected {n} modified image|Selected {n} modified images არჩეულია შეცვლილი გამოსახულებები: {n} +@msgplural Selected {n} image|Selected {n} images არჩეულია გამოსახულებები: {n} +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; დამალული ან ჩაკეტილია კიდევ: {n} +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; შაბლონის ფენაზეა კიდევ: {n} +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; იზოლირებული ჯგუფის გარეთაა კიდევ: {n} +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; ახლა ვერ აირჩევა კიდევ: {n} + # Compound Shapes add-on Compound Shapes შედგენილი ფიგურები Shape Mode Buttons Make Compound Shapes ფიგურის რეჟიმის ღილაკები ქმნის შედგენილ ფიგურებს diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index ac5d1193a..adbf82710 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5946,6 +5946,25 @@ breakText Glyphs 글리프 Minimum angle 최소 각도 Maximum angle 최대 각도 +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked 연결됨 선택 + Select Embedded 포함됨 선택 + Select Missing 없는 이미지 선택 + Select Modified 수정된 이미지 선택 + Select by Link Status 링크 상태로 선택 + Select by Link Status… 링크 상태로 선택… + Search images 이미지 검색 + No images match. 일치하는 이미지가 없습니다. +@msgplural Selected {n} linked image|Selected {n} linked images 연결된 이미지 {n}개를 선택했습니다 +@msgplural Selected {n} embedded image|Selected {n} embedded images 포함된 이미지 {n}개를 선택했습니다 +@msgplural Selected {n} missing image|Selected {n} missing images 없는 이미지 {n}개를 선택했습니다 +@msgplural Selected {n} modified image|Selected {n} modified images 수정된 이미지 {n}개를 선택했습니다 +@msgplural Selected {n} image|Selected {n} images 이미지 {n}개를 선택했습니다 +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}. {n}개는 숨겨졌거나 잠겨 있습니다 +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}. {n}개는 템플릿 레이어에 있습니다 +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}. {n}개는 격리된 그룹 밖에 있습니다 +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}. {n}개는 지금 선택할 수 없습니다 + # Compound Shapes add-on Compound Shapes 컴파운드 모양 Shape Mode Buttons Make Compound Shapes 모양 모드 버튼으로 컴파운드 모양 만들기 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 62385d6c9..5c00ac9f4 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -6275,6 +6275,25 @@ breakText Glyphs Glyphs Minimum angle Minimale hoek Maximum angle Maximale hoek +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Gekoppelde selecteren + Select Embedded Ingesloten selecteren + Select Missing Ontbrekende selecteren + Select Modified Gewijzigde selecteren + Select by Link Status Selecteren op koppelingsstatus + Select by Link Status… Selecteren op koppelingsstatus… + Search images Afbeeldingen zoeken + No images match. Geen afbeelding komt overeen. +@msgplural Selected {n} linked image|Selected {n} linked images {n} gekoppelde afbeelding geselecteerd|{n} gekoppelde afbeeldingen geselecteerd +@msgplural Selected {n} embedded image|Selected {n} embedded images {n} ingesloten afbeelding geselecteerd|{n} ingesloten afbeeldingen geselecteerd +@msgplural Selected {n} missing image|Selected {n} missing images {n} ontbrekende afbeelding geselecteerd|{n} ontbrekende afbeeldingen geselecteerd +@msgplural Selected {n} modified image|Selected {n} modified images {n} gewijzigde afbeelding geselecteerd|{n} gewijzigde afbeeldingen geselecteerd +@msgplural Selected {n} image|Selected {n} images {n} afbeelding geselecteerd|{n} afbeeldingen geselecteerd +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; nog {n} is verborgen of vergrendeld|{selected}; nog {n} zijn verborgen of vergrendeld +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; nog {n} staat op een sjabloonlaag|{selected}; nog {n} staan op sjabloonlagen +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; nog {n} staat buiten de geïsoleerde groep|{selected}; nog {n} staan buiten de geïsoleerde groep +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; nog {n} kan nu niet worden geselecteerd|{selected}; nog {n} kunnen nu niet worden geselecteerd + # Compound Shapes add-on Compound Shapes Samengestelde vormen Shape Mode Buttons Make Compound Shapes Vormmodus-knoppen maken samengestelde vormen diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 908f36c94..0754590d4 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -6220,6 +6220,25 @@ breakText Glyphs Glify Minimum angle Kąt minimalny Maximum angle Kąt maksymalny +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Zaznacz połączone + Select Embedded Zaznacz osadzone + Select Missing Zaznacz brakujące + Select Modified Zaznacz zmodyfikowane + Select by Link Status Zaznacz według stanu połączenia + Select by Link Status… Zaznacz według stanu połączenia… + Search images Szukaj obrazów + No images match. Żaden obraz nie pasuje. +@msgplural Selected {n} linked image|Selected {n} linked images Zaznaczono {n} połączony obraz|Zaznaczono {n} połączone obrazy|Zaznaczono {n} połączonych obrazów +@msgplural Selected {n} embedded image|Selected {n} embedded images Zaznaczono {n} osadzony obraz|Zaznaczono {n} osadzone obrazy|Zaznaczono {n} osadzonych obrazów +@msgplural Selected {n} missing image|Selected {n} missing images Zaznaczono {n} brakujący obraz|Zaznaczono {n} brakujące obrazy|Zaznaczono {n} brakujących obrazów +@msgplural Selected {n} modified image|Selected {n} modified images Zaznaczono {n} zmodyfikowany obraz|Zaznaczono {n} zmodyfikowane obrazy|Zaznaczono {n} zmodyfikowanych obrazów +@msgplural Selected {n} image|Selected {n} images Zaznaczono {n} obraz|Zaznaczono {n} obrazy|Zaznaczono {n} obrazów +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; jeszcze {n} jest ukryty lub zablokowany|{selected}; jeszcze {n} są ukryte lub zablokowane|{selected}; jeszcze {n} jest ukrytych lub zablokowanych +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; jeszcze {n} jest na warstwie szablonu|{selected}; jeszcze {n} są na warstwach szablonu|{selected}; jeszcze {n} jest na warstwach szablonu +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; jeszcze {n} jest poza izolowaną grupą|{selected}; jeszcze {n} są poza izolowaną grupą|{selected}; jeszcze {n} jest poza izolowaną grupą +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; jeszcze {n} nie można teraz zaznaczyć|{selected}; jeszcze {n} nie można teraz zaznaczyć|{selected}; jeszcze {n} nie można teraz zaznaczyć + # Compound Shapes add-on Compound Shapes Kształty złożone Shape Mode Buttons Make Compound Shapes Przyciski trybów kształtu tworzą kształty złożone diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index eb376906c..6ba1f7ff3 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -6438,6 +6438,25 @@ swatch Yellow Green Verde-amarelado Minimum angle Ângulo Mínimo Maximum angle Ângulo Máximo +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Selecionar vinculadas + Select Embedded Selecionar incorporadas + Select Missing Selecionar ausentes + Select Modified Selecionar modificadas + Select by Link Status Selecionar por status do vínculo + Select by Link Status… Selecionar por status do vínculo… + Search images Pesquisar imagens + No images match. Nenhuma imagem corresponde. +@msgplural Selected {n} linked image|Selected {n} linked images {n} imagem vinculada selecionada|{n} imagens vinculadas selecionadas +@msgplural Selected {n} embedded image|Selected {n} embedded images {n} imagem incorporada selecionada|{n} imagens incorporadas selecionadas +@msgplural Selected {n} missing image|Selected {n} missing images {n} imagem ausente selecionada|{n} imagens ausentes selecionadas +@msgplural Selected {n} modified image|Selected {n} modified images {n} imagem modificada selecionada|{n} imagens modificadas selecionadas +@msgplural Selected {n} image|Selected {n} images {n} imagem selecionada|{n} imagens selecionadas +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; mais {n} está oculta ou bloqueada|{selected}; mais {n} estão ocultas ou bloqueadas +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; mais {n} está em uma camada de modelo|{selected}; mais {n} estão em camadas de modelo +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; mais {n} está fora do grupo isolado|{selected}; mais {n} estão fora do grupo isolado +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; mais {n} não pode ser selecionada agora|{selected}; mais {n} não podem ser selecionadas agora + # Compound Shapes add-on Compound Shapes Formas Compostas Shape Mode Buttons Make Compound Shapes Os Botões de Modo de Forma Criam Formas Compostas diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index d700736c6..7be753b6f 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -6454,6 +6454,25 @@ shape Open Открытый Minimum angle Минимальный угол Maximum angle Максимальный угол +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Выделить связанные + Select Embedded Выделить внедрённые + Select Missing Выделить отсутствующие + Select Modified Выделить изменённые + Select by Link Status Выделить по состоянию связи + Select by Link Status… Выделить по состоянию связи… + Search images Поиск изображений + No images match. Нет подходящих изображений. +@msgplural Selected {n} linked image|Selected {n} linked images Выделено {n} связанное изображение|Выделено {n} связанных изображения|Выделено {n} связанных изображений +@msgplural Selected {n} embedded image|Selected {n} embedded images Выделено {n} внедрённое изображение|Выделено {n} внедрённых изображения|Выделено {n} внедрённых изображений +@msgplural Selected {n} missing image|Selected {n} missing images Выделено {n} отсутствующее изображение|Выделено {n} отсутствующих изображения|Выделено {n} отсутствующих изображений +@msgplural Selected {n} modified image|Selected {n} modified images Выделено {n} изменённое изображение|Выделено {n} изменённых изображения|Выделено {n} изменённых изображений +@msgplural Selected {n} image|Selected {n} images Выделено {n} изображение|Выделено {n} изображения|Выделено {n} изображений +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; ещё {n} скрыто или заблокировано|{selected}; ещё {n} скрыты или заблокированы|{selected}; ещё {n} скрыты или заблокированы +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; ещё {n} на слое-шаблоне|{selected}; ещё {n} на слоях-шаблонах|{selected}; ещё {n} на слоях-шаблонах +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; ещё {n} вне изолированной группы|{selected}; ещё {n} вне изолированной группы|{selected}; ещё {n} вне изолированной группы +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; ещё {n} сейчас нельзя выделить|{selected}; ещё {n} сейчас нельзя выделить|{selected}; ещё {n} сейчас нельзя выделить + # Compound Shapes add-on Compound Shapes Составные фигуры Shape Mode Buttons Make Compound Shapes Кнопки режимов фигур создают составные фигуры diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 85f4812d1..68eee3605 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -6421,6 +6421,25 @@ digits Hindi Hindi Minimum angle Minsta vinkel Maximum angle Största vinkel +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Markera länkade + Select Embedded Markera inbäddade + Select Missing Markera saknade + Select Modified Markera ändrade + Select by Link Status Markera efter länkstatus + Select by Link Status… Markera efter länkstatus… + Search images Sök bilder + No images match. Ingen bild matchar. +@msgplural Selected {n} linked image|Selected {n} linked images Markerade {n} länkad bild|Markerade {n} länkade bilder +@msgplural Selected {n} embedded image|Selected {n} embedded images Markerade {n} inbäddad bild|Markerade {n} inbäddade bilder +@msgplural Selected {n} missing image|Selected {n} missing images Markerade {n} saknad bild|Markerade {n} saknade bilder +@msgplural Selected {n} modified image|Selected {n} modified images Markerade {n} ändrad bild|Markerade {n} ändrade bilder +@msgplural Selected {n} image|Selected {n} images Markerade {n} bild|Markerade {n} bilder +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; {n} till är dold eller låst|{selected}; {n} till är dolda eller låsta +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; {n} till ligger på ett mallager|{selected}; {n} till ligger på mallager +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; {n} till ligger utanför den isolerade gruppen|{selected}; {n} till ligger utanför den isolerade gruppen +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; {n} till kan inte markeras nu|{selected}; {n} till kan inte markeras nu + # Compound Shapes add-on Compound Shapes Sammansatta former Shape Mode Buttons Make Compound Shapes Formlägesknapparna skapar sammansatta former diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 5c6446de7..184bd0b46 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -6322,6 +6322,25 @@ breakText Glyphs Glifler Minimum angle En Küçük Açı Maximum angle En Büyük Açı +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Bağlantılıları Seç + Select Embedded Gömülüleri Seç + Select Missing Eksikleri Seç + Select Modified Değiştirilenleri Seç + Select by Link Status Bağlantı Durumuna Göre Seç + Select by Link Status… Bağlantı Durumuna Göre Seç… + Search images Görüntülerde ara + No images match. Eşleşen görüntü yok. +@msgplural Selected {n} linked image|Selected {n} linked images {n} bağlantılı görüntü seçildi +@msgplural Selected {n} embedded image|Selected {n} embedded images {n} gömülü görüntü seçildi +@msgplural Selected {n} missing image|Selected {n} missing images {n} eksik görüntü seçildi +@msgplural Selected {n} modified image|Selected {n} modified images {n} değiştirilmiş görüntü seçildi +@msgplural Selected {n} image|Selected {n} images {n} görüntü seçildi +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; {n} tane daha gizli veya kilitli +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; {n} tane daha şablon katmanında +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; {n} tane daha izole grubun dışında +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; {n} tane daha şu anda seçilemiyor + # Compound Shapes add-on Compound Shapes Bileşik Şekiller Shape Mode Buttons Make Compound Shapes Şekil Modu Düğmeleri Bileşik Şekiller Yapar diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 4bb708340..f89aa17e9 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5965,6 +5965,25 @@ breakText Glyphs Гліфи Minimum angle Мінімальний кут Maximum angle Максимальний кут +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Виділити пов’язані + Select Embedded Виділити вбудовані + Select Missing Виділити відсутні + Select Modified Виділити змінені + Select by Link Status Виділити за станом зв’язку + Select by Link Status… Виділити за станом зв’язку… + Search images Пошук зображень + No images match. Немає відповідних зображень. +@msgplural Selected {n} linked image|Selected {n} linked images Виділено {n} пов’язане зображення|Виділено {n} пов’язані зображення|Виділено {n} пов’язаних зображень +@msgplural Selected {n} embedded image|Selected {n} embedded images Виділено {n} вбудоване зображення|Виділено {n} вбудовані зображення|Виділено {n} вбудованих зображень +@msgplural Selected {n} missing image|Selected {n} missing images Виділено {n} відсутнє зображення|Виділено {n} відсутні зображення|Виділено {n} відсутніх зображень +@msgplural Selected {n} modified image|Selected {n} modified images Виділено {n} змінене зображення|Виділено {n} змінені зображення|Виділено {n} змінених зображень +@msgplural Selected {n} image|Selected {n} images Виділено {n} зображення|Виділено {n} зображення|Виділено {n} зображень +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; ще {n} приховане або заблоковане|{selected}; ще {n} приховані або заблоковані|{selected}; ще {n} приховані або заблоковані +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; ще {n} на шарі-шаблоні|{selected}; ще {n} на шарах-шаблонах|{selected}; ще {n} на шарах-шаблонах +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; ще {n} поза ізольованою групою|{selected}; ще {n} поза ізольованою групою|{selected}; ще {n} поза ізольованою групою +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; ще {n} зараз не можна виділити|{selected}; ще {n} зараз не можна виділити|{selected}; ще {n} зараз не можна виділити + # Compound Shapes add-on Compound Shapes Складені фігури Shape Mode Buttons Make Compound Shapes Кнопки режимів фігур створюють складені фігури diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 330792405..a76ec9ebe 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5966,6 +5966,25 @@ breakText Glyphs Glyph Minimum angle Góc tối thiểu Maximum angle Góc tối đa +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked Chọn ảnh đã liên kết + Select Embedded Chọn ảnh đã nhúng + Select Missing Chọn ảnh bị thiếu + Select Modified Chọn ảnh đã sửa đổi + Select by Link Status Chọn theo trạng thái liên kết + Select by Link Status… Chọn theo trạng thái liên kết… + Search images Tìm ảnh + No images match. Không có ảnh nào khớp. +@msgplural Selected {n} linked image|Selected {n} linked images Đã chọn {n} ảnh đã liên kết +@msgplural Selected {n} embedded image|Selected {n} embedded images Đã chọn {n} ảnh đã nhúng +@msgplural Selected {n} missing image|Selected {n} missing images Đã chọn {n} ảnh bị thiếu +@msgplural Selected {n} modified image|Selected {n} modified images Đã chọn {n} ảnh đã sửa đổi +@msgplural Selected {n} image|Selected {n} images Đã chọn {n} ảnh +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected}; thêm {n} ảnh bị ẩn hoặc khóa +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected}; thêm {n} ảnh nằm trên lớp mẫu +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected}; thêm {n} ảnh nằm ngoài nhóm đã tách biệt +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected}; thêm {n} ảnh hiện không thể chọn + # Compound Shapes add-on Compound Shapes Hình phức hợp Shape Mode Buttons Make Compound Shapes Nút chế độ hình dạng tạo hình phức hợp diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 48f040a9c..c1945d726 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -6206,6 +6206,25 @@ breakText Glyphs 字形 Minimum angle 最小角度 Maximum angle 最大角度 +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked 选择已链接 + Select Embedded 选择嵌入 + Select Missing 选择缺失 + Select Modified 选择已修改 + Select by Link Status 按链接状态选择 + Select by Link Status… 按链接状态选择… + Search images 搜索图像 + No images match. 没有匹配的图像。 +@msgplural Selected {n} linked image|Selected {n} linked images 已选择 {n} 个已链接的图像 +@msgplural Selected {n} embedded image|Selected {n} embedded images 已选择 {n} 个嵌入的图像 +@msgplural Selected {n} missing image|Selected {n} missing images 已选择 {n} 个缺失的图像 +@msgplural Selected {n} modified image|Selected {n} modified images 已选择 {n} 个已修改的图像 +@msgplural Selected {n} image|Selected {n} images 已选择 {n} 个图像 +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected};另有 {n} 个已隐藏或锁定 +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected};另有 {n} 个位于模板图层上 +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected};另有 {n} 个位于隔离的编组之外 +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected};另有 {n} 个现在无法选择 + # Compound Shapes add-on Compound Shapes 复合形状 Shape Mode Buttons Make Compound Shapes 形状模式按钮创建复合形状 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index f0c659b50..d7347d174 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -6003,6 +6003,25 @@ breakText Glyphs 字符 Minimum angle 最小角度 Maximum angle 最大角度 +# Selection Tools add-on: Select by Link Status (Object › Plug-ins › Selection Tools, the Links panel menu, #5526). + Select Linked 選取已連結 + Select Embedded 選取已內嵌 + Select Missing 選取遺失 + Select Modified 選取已修改 + Select by Link Status 依連結狀態選取 + Select by Link Status… 依連結狀態選取… + Search images 搜尋影像 + No images match. 沒有符合的影像。 +@msgplural Selected {n} linked image|Selected {n} linked images 已選取 {n} 個已連結的影像 +@msgplural Selected {n} embedded image|Selected {n} embedded images 已選取 {n} 個已內嵌的影像 +@msgplural Selected {n} missing image|Selected {n} missing images 已選取 {n} 個遺失的影像 +@msgplural Selected {n} modified image|Selected {n} modified images 已選取 {n} 個已修改的影像 +@msgplural Selected {n} image|Selected {n} images 已選取 {n} 個影像 +@msgplural {selected}; {n} more is hidden or locked|{selected}; {n} more are hidden or locked {selected};另有 {n} 個已隱藏或鎖定 +@msgplural {selected}; {n} more is on a template layer|{selected}; {n} more are on template layers {selected};另有 {n} 個位於範本圖層上 +@msgplural {selected}; {n} more is outside the isolated group|{selected}; {n} more are outside the isolated group {selected};另有 {n} 個位於隔離的群組之外 +@msgplural {selected}; {n} more can't be selected now|{selected}; {n} more can't be selected now {selected};另有 {n} 個現在無法選取 + # Compound Shapes add-on Compound Shapes 複合形狀 Shape Mode Buttons Make Compound Shapes 形狀模式按鈕製作複合形狀 diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index a41920416..ab50fe76d 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -64,6 +64,8 @@ use bar::{menu_body, pointer_reaches_title}; pub use context::{context_items, context_menu_body, ruler_menu_body, ruler_unit_items}; pub use entries::{MenuEntry, Row, context_entries, entry, menu_entries, pretty_shortcut, shown_shortcut}; pub(crate) use invoke::graph_dialog; +#[cfg(test)] +pub(crate) use invoke::with_held_keys; pub use invoke::{click_target, invoke, invoke_from_system_menu}; #[cfg(test)] pub(crate) use labels::lists_plugin; diff --git a/crates/ui-egui/src/menus/invoke.rs b/crates/ui-egui/src/menus/invoke.rs index 963eab802..5b0d0f3c4 100644 --- a/crates/ui-egui/src/menus/invoke.rs +++ b/crates/ui-egui/src/menus/invoke.rs @@ -15,10 +15,13 @@ pub fn click_target(label: &str, id: &str, p: &Value) -> (String, Value) { /// What an in-window menu click on `target` (a [`click_target`]) runs with `held` held: Object › /// Unlock All and Object › Show All chosen with Shift+Alt (Shift+Option) act only inside the groups -/// that hold the selected objects (`scope: "group"`). -pub(super) fn with_held_keys(target: (String, Value), held: egui::Modifiers) -> (String, Value) { +/// that hold the selected objects (`scope: "group"`); Select Linked, Embedded, Missing and Modified +/// chosen with Shift add to the selection (`add: true`). +pub(crate) fn with_held_keys(target: (String, Value), held: egui::Modifiers) -> (String, Value) { match target.0.as_str() { "object.unlockAll" | "object.showAll" if held.shift && held.alt => (target.0, json!({"scope": "group"})), + // Select Linked, Embedded, Missing and Modified with Shift add to the selection. + crate::addons::select::link_status::COMMAND if held.shift => (target.0, crate::addons::select::link_status::with_shift(target.1)), _ => target, } } diff --git a/crates/ui-egui/src/menus/run.rs b/crates/ui-egui/src/menus/run.rs index e9776e78f..1bb536f76 100644 --- a/crates/ui-egui/src/menus/run.rs +++ b/crates/ui-egui/src/menus/run.rs @@ -578,6 +578,7 @@ fn run_ui_part6(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option crate::addons::pathtools::corner::open(app), crate::addons::pathtools::shared_edge::COMMAND => crate::addons::pathtools::shared_edge::open(app), crate::addons::arrange::ZOOM => crate::addons::arrange::zoom(app), + crate::addons::select::link_status::COMMAND => crate::addons::select::link_status::run(app, p), crate::addons::document::SAVE_COPY => crate::addons::document::save_copy(app, p), crate::addons::document::EXPORT_ALL => crate::addons::document::export_all(app, p), crate::addons::document::BATCH_TRACE => crate::addons::document::batch_trace(app, p), diff --git a/crates/ui-egui/src/menus/state.rs b/crates/ui-egui/src/menus/state.rs index 673ceb81f..78553cc7b 100644 --- a/crates/ui-egui/src/menus/state.rs +++ b/crates/ui-egui/src/menus/state.rs @@ -135,7 +135,7 @@ fn swapped_out(app: &VectorcraftApp, id: &str) -> bool { /// or off), or `None` while they leave it out: an empty slot ([`hidden_when_disabled`]) or an item /// another stands in for ([`swapped_out`]). pub fn shown_state(app: &VectorcraftApp, id: &str, p: &Value) -> Option<(bool, Option)> { - let en = enabled(app, id); + let en = enabled(app, id) && (id != crate::addons::select::link_status::COMMAND || crate::addons::select::link_status::enabled(app, p)); if (!en && hidden_when_disabled(id)) || swapped_out(app, id) { return None; } diff --git a/crates/ui-egui/src/panels/links.rs b/crates/ui-egui/src/panels/links.rs index d924d06f3..d27603424 100644 --- a/crates/ui-egui/src/panels/links.rs +++ b/crates/ui-egui/src/panels/links.rs @@ -4,7 +4,8 @@ //! from the last row clicked without Shift to it (with Ctrl/Cmd, adds them); the buttons Relink, //! Go To, Update and Edit Original act on the selected rows; Link Info shows the details of the //! first. The flyout adds Relink to Folder, Find in Folder (Find Linked Files: -//! `ui.findLinksInFolder`), Embed / Unembed, Placement Options, Show filters, Sort, thumbnail size +//! `ui.findLinksInFolder`), Embed / Unembed, Placement Options, Select Linked / Embedded / Missing / +//! Modified (`addon.select.byLinkStatus`, Shift adds), Show filters, Sort, thumbnail size //! and Show in Folder. //! //! Everything runs through the `links.*` commands (`links.editOriginal` and `links.reveal` are UI @@ -597,6 +598,17 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { crate::menus::invoke(app, "links.reveal", json!({})); } ui.separator(); + // Select Linked, Embedded, Missing and Modified: every such image in the document, not only + // the rows shown; with Shift, added to the selection. + use crate::addons::select::link_status; + for (i, (status, label, _)) in link_status::STATUSES.iter().enumerate() { + if menu_item(ui, tl!(label), app.ui.link_statuses.has.get(i).copied().unwrap_or(false), false) { + let p = json!({ "status": status }); + let p = if ui.input(|i| i.modifiers.shift) { link_status::with_shift(p) } else { p }; + crate::menus::invoke(app, link_status::COMMAND, p); + } + } + ui.separator(); for (id, label) in SHOW { if menu_item(ui, tl!(label), true, o.show == id) { set_options(ui.ctx(), |o| o.show = id); diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 2baf6f6ee..044f3e66b 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -382,6 +382,11 @@ pub struct UiState { /// is open. Not saved. #[serde(skip)] pub context_intertwine: Option<(u64, vectorcraft_doc::NodeId, vectorcraft_geom::Point, vectorcraft_doc::NodeId)>, + /// Which link statuses the active document's images and placed documents have, for Select › + /// Linked, Embedded, Missing and Modified to be on only when some do (refreshed by + /// [`crate::addons::select::link_status::refresh`]). Not saved. + #[serde(skip)] + pub link_statuses: crate::addons::select::link_status::Present, /// Last tool shown in each toolbar slot (keyed by the slot's first tool id). #[serde(default)] pub slot_tool: std::collections::BTreeMap, @@ -942,6 +947,7 @@ impl Default for UiState { shape_modes_compound: false, context_locked: None, context_intertwine: None, + link_statuses: Default::default(), slot_tool: Default::default(), floating_flyouts: vec![], toolbar_edits: ToolbarEdits::default(), diff --git a/docs/mcp.md b/docs/mcp.md index 1e6fbc204..fcbb492fd 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -6864,6 +6864,30 @@ The dialog lists every artboard with its number, name and size in the document's name; a click chooses one artboard, Shift-click adds a range and Cmd/Ctrl-click toggles one, and Select All / None act on the rows shown (rows hidden by the search stay chosen). It opens with the active artboard chosen and Overlapping. +`addon.select.byLinkStatus {status, ids?, add?, zoom?}` (Select Linked / Embedded / Missing / Modified and Select by +Link Status…, #5526) selects the image objects and placed documents whose link has a status, as `links.list` reports +it: `linked` (its file found unchanged; `links.list` says `ok`), `embedded`, `missing` or `modified` (its file changed +since it was read). `status` is one of them or a list of them (at least one; one listed twice counts once). `ids` picks +only among those objects (other objects among them are passed over); `add: true` adds them to the selection instead of +replacing it; `zoom: true` asks for Zoom to Selection afterwards, which the desktop app runs (`addon.arrange.zoomSelection`) +when something was selected, and which other callers get back as `zoom: true`. Objects that are hidden or locked, inside +a hidden or locked object or layer, on a template layer, on a locked artboard while nothing is isolated, or outside the +isolated group or layer are left out and listed with the reason. Only the selection changes, so there is nothing to +undo. It returns `{count, selected: [ids] (bottom first), skipped: [{id, reason: hidden|locked|template|isolation}]}`. +A missing or unknown status, an id naming no object, or a value of the wrong type is an error, and the selection is left +as it was. + +```json +{"name":"run_command","arguments":{"command":"addon.select.byLinkStatus","params":{"status":["missing","modified"],"zoom":true}}} +``` + +In the desktop app, Object › Plug-ins › Selection Tools and the Links panel menu have Select Linked, Select Embedded, +Select Missing and Select Modified, each off when no image has that status; with Shift held they add to the selection. +The status bar says what was selected and what was left out, e.g. "Selected 9 missing images; 3 more are hidden or +locked". Select by Link Status… lists every image and placed document with its name, status and format, searches by +name and filters by status (Show), chooses rows with checkboxes (All / None act on the rows shown), and on Select +selects the rows chosen, zooming to them when Zoom to Selection is on (the default). + ### Vector Halftone (live) `addon.color.halftone` (Effect › Color Effects › Vector Halftone…, also in Object › Plug-ins › Color Effects) draws its diff --git a/docs/roadmap.md b/docs/roadmap.md index 6831863a9..0db3a8435 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap: milestones and what's next -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (Select by Artboards add-on selects by overlap, inside or outside several artboards; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (Selection Tools add-on: Select by Link Status selects images and placed documents that are linked, embedded, missing or modified, #5526; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -28,6 +28,7 @@ M7 type: Touch Type moves a right-to-left character along its line and up from i | Date | Change | Summary | |---|---|---| +| 2026-11-05 | minor | Selection Tools add-on: Select Linked, Embedded, Missing and Modified select the images and placed documents with that link status (Object › Plug-ins › Selection Tools and the Links panel menu; Shift adds), and Select by Link Status… lists them by name, status and format to pick from, optionally zooming to them; what is hidden, locked, on a template layer or outside the isolated group is left out and the status bar says how many (#5526, a VectorCraft addition); scores and estimates unchanged (estimated). | | 2026-11-04 | minor | M7: Find Font's Replace With Font lists family and style rows with a search, and the list and the document's font list scroll on their own (#4972); scores and estimates unchanged. | | 2026-11-04 | minor | Selection Tools add-on: Select by Artboards selects the objects overlapping, fully inside or fully outside several chosen artboards, from a dialog that lists, searches and multi-selects the artboards and fits one in the window on double-click (#5525, #5548, #5549, a VectorCraft addition); scores and estimates unchanged. | | 2026-11-04 | minor | New Document: the units chosen in Preset Details belong to the chosen preset; Recent and Saved cards keep the units their document or preset was made in, and the built-in tabs still follow the chosen units (#2078). Scores and estimates unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 004188166..27b63a921 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; Optical kerning spaces pairs by the font's own stem spacing instead of a fixed tight gap; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (Selection Tools add-on: Select by Link Status selects images and placed documents that are linked, embedded, missing or modified, #5526; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -23,6 +23,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| +| 2026-11-05 | minor | Selection Tools add-on: Select Linked, Embedded, Missing and Modified select the images and placed documents with that link status (Object › Plug-ins › Selection Tools and the Links panel menu; Shift adds), and Select by Link Status… lists them by name, status and format to pick from, optionally zooming to them; what is hidden, locked, on a template layer or outside the isolated group is left out and the status bar says how many (#5526, a VectorCraft addition); scores and estimates unchanged (estimated). | | 2026-11-05 | minor | G12: Hyphenation › Default Language sets the language of new type unless Document Setup chose one, stored as a tag; each run hyphenates in its own language, English only so far, so 2 Preferences options stay unused (#394) | | 2026-11-05 | minor | Type Tools add-on: Release Type on a Path (a VectorCraft addition, #5532): evidence added; scores unchanged. | | 2026-11-04 | minor | Path Precision Tools add-on (a VectorCraft addition): Set Corners… and `addon.pathtools.cornerSet` `minAngle`/`maxAngle` round only the corners whose angle is in a range (#5545); evidence added; scores unchanged. | @@ -736,6 +737,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur The Lasso picks anchor points only on art a click can pick, and the Add Anchor Point, Delete Anchor Point, Anchor Point and Scissors tools pick anchor points and segments only on the selected paths and on art a click can pick: not on a Shaper Group's original shapes outside Construction mode, and in isolation mode only inside the isolated group or object. Smart Guides and Snap to Point do not snap to a Shaper Group's original shapes, and the reference point of the transform tools snaps to one only when it is selected, or under the pointer in Construction mode. Enclosing Lasso provides whole-object enclosure selection under Object › Plug-ins and its own heading in All Tools. It has no default shortcut or Basic toolbar slot. It follows path curves and keeps partially enclosed objects out, including objects crossing a concave part of the loop. Ordinary group members can be selected individually; compound paths remain whole. This is a VectorCraft addition; scores and estimates are unchanged. Select by Artboards (Object › Plug-ins › Selection Tools, a VectorCraft addition: #5525, #5548, #5549) selects the objects overlapping, fully inside or fully outside one or more chosen artboards (`addon.select.byArtboards {artboards, relation}`). Overlapping is All on Active Artboard's test, shared as `select::on_artboard`; Fully inside means within one artboard, edges included; Fully outside means on none. It looks at what Select All takes, so hidden, locked and locked-artboard art stays out. Its dialog lists each artboard's number, name and size in the document's units, filters by name, chooses rows with click, Shift-click and Cmd/Ctrl-click, and fits a row's artboard in the window on double-click (`view.goToArtboard`). Tests: `crates/engine/src/cmd/addons/select/tests.rs`, `crates/ui-egui/src/addons/select/tests.rs`, `crates/engine/tests/command_sweep.rs`. + Select by Link Status (Object › Plug-ins › Selection Tools and the Links panel menu, a VectorCraft addition: #5526) selects the image objects and placed documents whose link is linked (its file found unchanged), embedded, missing or modified, as `links.list` reports it (`addon.select.byLinkStatus {status, ids?, add?, zoom?}`); Select Linked, Embedded, Missing and Modified are off when no image has that status and add to the selection with Shift. Objects that are hidden or locked, inside a hidden or locked object or layer, on a template layer, on a locked artboard, or outside the isolated group are left out (the `select::is_selectable` test) and returned with the reason, and the status bar says how many, in plural forms of every catalog. Select by Link Status… lists each image and placed document with its name, status and format, searches by name, filters by status, picks rows with checkboxes and All / None, and zooms to the selection with the existing Zoom to Selection (`addon.arrange.zoomSelection`). Missing-link markers on the canvas are #5547. Tests: `crates/engine/src/cmd/addons/select/tests.rs` (`by_link_status`), `crates/ui-egui/src/addons/select/link_status/tests.rs`, `crates/engine/tests/command_sweep.rs`. A marquee selects a clip group, or an object on a layer with a clipping mask, only when it reaches the area inside the clipping path, and it measures the art only within that path's bounds. Within the bounds of a mask that is not a rectangle, it can still select art that the mask hides. Direct Selection's marquee still selects the hidden anchor points inside it (#2345). A marquee selects a Shaper Group only when it reaches the art the group shows, not the original shapes kept under it, and Direct Selection's clicks and marquee do not select those shapes' anchor points. In Construction mode a marquee selects the original shapes, and Direct Selection selects their anchor points. While an opacity mask or a pattern is edited, a Shaper double-click on a Shaper Group in the mask or pattern art selects the original shape under the pointer without entering Construction mode, the Shaper Group's mode widget does not show, and a Shaper scribble, Release and Expand keep the editing layer isolated. diff --git a/docs/ui-parity.md b/docs/ui-parity.md index fca5458f0..560fc3edb 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI and interaction parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; Select by Artboards add-on selects by overlap, inside or outside several artboards; Cmd+Space keeps the active drawing cursor during its drag; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (Selection Tools add-on: Select by Link Status selects images and placed documents that are linked, embedded, missing or modified, #5526; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) ## Feature submission evidence @@ -30,6 +30,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard | Date | Change | Summary | |---|---|---| +| 2026-11-05 | minor | Selection Tools add-on: Select Linked, Embedded, Missing and Modified select the images and placed documents with that link status (Object › Plug-ins › Selection Tools and the Links panel menu; Shift adds), and Select by Link Status… lists them by name, status and format to pick from, optionally zooming to them; what is hidden, locked, on a template layer or outside the isolated group is left out and the status bar says how many (#5526, a VectorCraft addition); scores and estimates unchanged (estimated). | | 2026-11-05 | minor | G12: Hyphenation › Default Language sets the language of new type unless Document Setup chose one, stored as a tag; each run hyphenates in its own language, English only so far, so 2 Preferences options stay unused (#394) | | 2026-11-05 | minor | Gradient panel: Delete Stop sits at the right end of the ramp, so it shows whenever the ramp does instead of under the stop fields, where a short panel hid it (#5403); scores and estimates unchanged | | 2026-11-04 | minor | Artboards panel: a Shift- or Ctrl/Cmd-click on a row selects several artboards with any tool, switching to the Artboard tool first, so a preset, orientation, size or Fill set in Properties or the Control bar goes to every selected artboard; Fill used to set the active artboard only (#5698); scores and estimates unchanged | @@ -395,6 +396,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po | Color Effects add-on (VectorCraft addition): live Exposure, Desaturate and Invert Colors in Effect › Color Effects and Object › Plug-ins › Color Effects; its panel picks a colour adjustment and its values and adds it as a live effect or applies it as a filter, into the colours, as one undo step; Select by Color Kind selects the objects that use spot colours, carry colour effects, or are RGB, CMYK or grayscale images; Vector Halftone draws an object as halftone dots live (the dots Object › Vector Halftone makes), which Expand Appearance and the exporters write as paths; Duotone prints each colour's tone with one to four inks (a tint of a spot tone's swatch, or the CMYK mix of several), live or as a filter, its dialog a row per tone and the picked tone's curve; Print Ink Fixes (a CMYK document's ink limit, white overprint removed, black overprint set) opens its options from the submenu and the panel's Inks | done | `addon.color.*`, `crates/ui-egui/src/addons/color.rs` and its tests; not a feature of the reference app, so it is not scored | | Color Effects add-on (VectorCraft addition): Object › Plug-ins › Color Effects › Find Nearest Swatch… picks a Library (Document Swatches, a built-in library, or a library or colour book the user saved or loaded) and Fill and Stroke, lists each colour of the selection with its three closest swatches (chip, name, ΔE 2000), and Apply gives each colour its nearest swatch (global and spot swatches linked; a library's swatches added to the document first), one undo step | done | #4923; `addon.color.nearestSwatch`, `addon.color.applyNearestSwatch`, `crates/engine/src/cmd/addons/color/nearest.rs`, `crates/ui-egui/src/addons/color/nearest.rs` and their tests; not a feature of the reference app, so it is not scored | | Selection Tools add-on (VectorCraft addition): Object › Plug-ins › Selection Tools › Select by Artboards… lists every artboard (number, name, width × height in the document's units), filters them by name, chooses several (click, Shift-click for a range, Cmd/Ctrl-click to toggle; Select All / None act on the rows shown; a count of those chosen), fits a row's artboard in the window on double-click and makes it active, and on OK selects the objects overlapping, fully inside or fully outside the chosen artboards (Overlapping is All on Active Artboard's test) | done | #5525, #5548, #5549; `addon.select.byArtboards`, `crates/engine/src/cmd/addons/select.rs`, `crates/ui-egui/src/addons/select.rs` and their tests; not a feature of the reference app | +| Selection Tools add-on (VectorCraft addition): Select Linked, Select Embedded, Select Missing and Select Modified in Object › Plug-ins › Selection Tools and the Links panel menu (off when no image has that status; Shift adds to the selection) select the images and placed documents with that link status, leaving out hidden, locked, template-layer and outside-isolation ones, which the status bar counts (e.g. "Selected 9 missing images; 3 more are hidden or locked"); Select by Link Status… lists them with name, status and format, searches by name, filters by status, picks with checkboxes and All / None, and selects with Zoom to Selection on by default | done | #5526; `addon.select.byLinkStatus`, `crates/engine/src/cmd/addons/select.rs`, `crates/ui-egui/src/addons/select/link_status.rs` and their tests; not a feature of the reference app | ## Look and feel