From c0b9eed99f70e783a83361326e90d82c4bf97336 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 13 Nov 2026 19:24:07 -0800 Subject: [PATCH] Links: a web open with missing and modified links shows both lines On the web, a document that opened with linked files it can't read and linked files that changed on disk showed only the line about the changed files when Update Links doesn't ask. The status bar now shows the line about the unreadable files and lists the line about the changed files as the first note in its hover, ahead of the open's notes. With Ask When Modified the update question opens and the line about the unreadable files stays, as before. --- crates/ui-egui/src/dialogs/missing_links.rs | 70 +++++++++++++++++---- docs/target-app-parity.md | 1 + 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/crates/ui-egui/src/dialogs/missing_links.rs b/crates/ui-egui/src/dialogs/missing_links.rs index 70c2370f1b..6de49453b3 100644 --- a/crates/ui-egui/src/dialogs/missing_links.rs +++ b/crates/ui-egui/src/dialogs/missing_links.rs @@ -43,26 +43,27 @@ fn ids(rows: &[Value]) -> Vec { /// then about its modified ones, then about the fonts it opened without /// ([`super::missing_fonts`]). The web reads no linked files, so missing ones just show their /// previews there. The status bar shows what it says about the linked files followed by the open's -/// `notes` ([`crate::io::show_opened`]). +/// `notes` ([`crate::io::show_opened`]). On the web, with both missing and modified files and +/// Update Links not asking, the line about the missing files comes first and the line about the +/// modified ones is the first of the notes. pub fn after_open(app: &mut VectorcraftApp, r: &Value, notes: Vec) { after_open_for(app, r, notes, cfg!(target_arch = "wasm32")); } -fn after_open_for(app: &mut VectorcraftApp, r: &Value, notes: Vec, web: bool) { +fn after_open_for(app: &mut VectorcraftApp, r: &Value, mut notes: Vec, web: bool) { // The fonts' dialog waits for these questions (one dialog at a time). super::missing_fonts::after_open(app, r); let rows = |k: &str| r[k].as_array().cloned().unwrap_or_default(); let (missing, modified) = (rows("missingLinks"), ids(&rows("modifiedLinks"))); let notice = if missing.is_empty() || web { - let unreadable = (!missing.is_empty()).then(|| { - crate::i18n::msg_n( - missing.len() as u64, - "{n} linked file can't be read here: its preview shows", - "{n} linked files can't be read here: their previews show", - &[], - ) - }); - ask_update(app, modified).or(unreadable) + let unreadable = (!missing.is_empty()).then(|| unreadable_status(missing.len())); + match (ask_update(app, modified), unreadable) { + (Some(changed), Some(unreadable)) => { + notes.insert(0, changed); + Some(unreadable) + } + (changed, unreadable) => changed.or(unreadable), + } } else { open(app, missing, modified, ¬es); None @@ -71,6 +72,16 @@ fn after_open_for(app: &mut VectorcraftApp, r: &Value, notes: Vec, web: super::settle(app); } +/// The status bar's line for `n` linked files the web can't read. +fn unreadable_status(n: usize) -> String { + crate::i18n::msg_n( + n as u64, + "{n} linked file can't be read here: its preview shows", + "{n} linked files can't be read here: their previews show", + &[], + ) +} + fn open(app: &mut VectorcraftApp, missing: Vec, modified: Vec, notes: &[String]) { let fields = json!({ "missing": missing, "modified": modified, "notes": notes, "applyToAll": false, "path": "" }); app.ui.dialog = Some(Dialog::new(KIND, fields)); @@ -346,6 +357,43 @@ mod tests { assert_eq!((app.ui.status.as_str(), &app.ui.status_notes), (notice, &None)); } + /// On the web, an open with linked files it can't read and modified ones, with Update Links + /// not asking: the status bar shows the line about the unreadable files and counts the line + /// about the modified ones as the first note, ahead of the open's notes, and keeps them all + /// for its hover. With Ask When Modified the update question opens and the line about the + /// unreadable files stays. + #[test] + fn a_web_open_with_unreadable_and_modified_links_keeps_both_lines() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + app.session.prefs.update_links = "manually".into(); + let missing = json!([{ "name": "a.png", "path": "/gone/a.png", "ids": [1] }, { "name": "b.png", "path": "/gone/b.png", "ids": [2] }]); + let r = json!({ "missingLinks": missing, "modifiedLinks": [{ "ids": [3] }] }); + let unreadable = "2 linked files can't be read here: their previews show"; + let changed = changed_on_disk_status(1); + + let notes = vec!["a note".to_string(), "another note".to_string()]; + after_open_for(&mut app, &r, notes.clone(), true); + let kept = [vec![changed.clone()], notes].concat(); + assert_eq!(app.ui.status, crate::io::opened_after_notice(unreadable, 3, &changed)); + assert_eq!(app.ui.status_notes, Some((app.ui.status.clone(), kept))); + assert!(app.ui.dialog.is_none()); + + after_open_for(&mut app, &r, vec![], true); + assert_eq!(app.ui.status, crate::io::opened_after_notice(unreadable, 1, &changed)); + assert_eq!(app.ui.status_notes, Some((app.ui.status.clone(), vec![changed.clone()]))); + + // One kind of line alone shows as before. + after_open_for(&mut app, &json!({ "modifiedLinks": [{ "ids": [3] }] }), vec![], true); + assert_eq!((app.ui.status.clone(), &app.ui.status_notes), (changed, &None)); + + app.session.prefs.update_links = "askWhenModified".into(); + after_open_for(&mut app, &r, vec![], true); + let d = app.ui.dialog.as_ref().expect("asks"); + assert_eq!((d.kind.as_str(), d.str("__command")), (super::super::confirm::KIND, "links.update".to_string())); + assert_eq!((app.ui.status.as_str(), &app.ui.status_notes), (unreadable, &None)); + } + /// The line about modified linked files that follows the Linked File Not Found dialog keeps the /// notes of that open, also when another message was shown meanwhile. An open without notes /// shows the line alone, even while the status bar shows an earlier open's notes, and a later diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 296bd84b2b..63cabcab30 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1771,6 +1771,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - On Windows, a link on a drive mapped to a network share other than the document's (`Y:\…`) is missing, with the reason that the path is on another computer, as a link stored with that share's own path is; links on this computer's drives and on the document's share are followed. - In the Links panel, Shift-click selects a range of links and Ctrl/Cmd-click adds or removes one. - When a document opens with notes and with linked files that changed on disk (Update Links: Manually) or, on the web, can't be read, the status bar shows the line about the linked files, then counts the notes and names the first, and hovering it lists the notes. The same holds when the line about modified files follows the Linked File Not Found dialog, and File › New from Template… shows the notes of the file it opens as File › Open does (`an_open_with_modified_links_keeps_link_notice_and_import_notes_reachable`, `new_from_template_preserves_open_warnings`, `an_open_with_unreadable_links_keeps_import_notes_reachable`, `modified_links_after_the_missing_ones_keep_the_open_notes`, `combined_open_and_link_status_lists_notes_on_hover_and_for_accessibility`). + - On the web, when a document opens with linked files that can't be read and linked files that changed on disk (Update Links: Manually), the status bar shows the line about the unreadable files, and the line about the changed files is the first of the notes its hover lists, ahead of the open's notes. With Ask When Modified the update question opens and the line about the unreadable files stays (`a_web_open_with_unreadable_and_modified_links_keeps_both_lines`). - Linked images and placed documents kept only in symbols, graph designs, patterns or opacity masks are listed in the Links panel after the layers' own, one row per stored object, with their status in its Missing and Modified filters, in Check Links, in the missing and modified files reported when a document opens, in the link watch and in Find in Folder; Relink, Update, Embed, Placement Options and Link Info act on them, and Go To says they have no place on the canvas. Opening a document gives such an object whose id is 0 or repeats another object's a new id. - Linked files another app changes while their document is open follow Preferences › File Handling › Update Links, as they do when it opens: Automatically reads them again (an Update Links undo step), Ask When Modified offers to, Manually leaves them modified in the Links panel. The desktop app compares the active document’s linked files by size and modification time every two seconds and when its window comes back to the front, on a worker thread; a file still being written is tried again (`links.updateChanged`). - A look at the linked files whose worker thread ends without a result, such as after a panic, no longer stops later looks: the app clears it, stops asking for a frame every 100 ms, and the next look starts on schedule (`crates/engine/src/tests/linkwatch.rs`, `crates/ui-egui/src/tests/place.rs`).