| author | |
| committer | |
| log | ec793693c96b0c8395e2a0d8f93bc6fd6af9b469 |
| tree | 9bfb50f054f8660c2537582e1baad5e98a4bf7c2 |
| parent | 2b462d10dda0898b5f553701b46982d68bfc8661 |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
Lock All Sections stood enabled with no protected section unlocked; the File
menu's and palette's Close This Notebook on an iCloud notebook, which its
context menu already disables, since the app's iCloud folder lists every
notebook in it; and Notebook Recycle Bin on a notebook whose bin holds no
sections, where it ended in an alert.
Assisted-by: claude-opus-5.54 files changed, 28 insertions(+), 4 deletions(-)
crates/snowbound/src/commands.rs+11-3| ... | @@ -1152,7 +1152,13 @@ impl State { | ... | @@ -1152,7 +1152,13 @@ impl State { |
| 1152 | | Id::CustomizeTags | 1152 | | Id::CustomizeTags |
| 1153 | | Id::Help | 1153 | | Id::Help |
| 1154 | | Id::CheckForUpdates => enabled(!modal), | 1154 | | Id::CheckForUpdates => enabled(!modal), |
| 1155 | Id::CloseNotebook => enabled(!modal && self.notebook().is_some()), | 1155 | // The app's iCloud Drive folder lists every notebook in it. |
| 1156 | Id::CloseNotebook => enabled( | ||
| 1157 | !modal | ||
| 1158 | && self | ||
| 1159 | .notebook() | ||
| 1160 | .is_some_and(|library| !crate::manage::in_icloud_folder(&library.location)), | ||
| 1161 | ), | ||
| 1156 | Id::OpenShared => enabled(!modal && cfg!(feature = "live")), | 1162 | Id::OpenShared => enabled(!modal && cfg!(feature = "live")), |
| 1157 | #[cfg(feature = "live")] | 1163 | #[cfg(feature = "live")] |
| 1158 | Id::LiveShare => enabled( | 1164 | Id::LiveShare => enabled( |
| ... | @@ -1169,7 +1175,7 @@ impl State { | ... | @@ -1169,7 +1175,7 @@ impl State { |
| 1169 | enabled: !modal | 1175 | enabled: !modal |
| 1170 | && self | 1176 | && self |
| 1171 | .notebook() | 1177 | .notebook() |
| 1172 | .is_some_and(|library| library.catalog().is_some()), | 1178 | .is_some_and(|library| self.recycle_bin_available(library)), |
| 1173 | checked: Some(self.in_recycle_bin()), | 1179 | checked: Some(self.in_recycle_bin()), |
| 1174 | }, | 1180 | }, |
| 1175 | Id::EmptyRecycleBin => enabled( | 1181 | Id::EmptyRecycleBin => enabled( |
| ... | @@ -1195,7 +1201,9 @@ impl State { | ... | @@ -1195,7 +1201,9 @@ impl State { |
| 1195 | .is_some_and(|library| self.reads.shown(&library.location)), | 1201 | .is_some_and(|library| self.reads.shown(&library.location)), |
| 1196 | ), | 1202 | ), |
| 1197 | }, | 1203 | }, |
| 1198 | Id::LockAll => enabled(!modal), | 1204 | Id::LockAll => { |
| 1205 | enabled(!modal && self.notebooks.iter().any(|library| library.any_unlocked())) | ||
| 1206 | } | ||
| 1199 | Id::ShowNotebook => enabled( | 1207 | Id::ShowNotebook => enabled( |
| 1200 | !modal | 1208 | !modal |
| 1201 | && self | 1209 | && self |
crates/snowbound/src/library.rs+5| ... | @@ -1105,6 +1105,11 @@ impl Library { | ... | @@ -1105,6 +1105,11 @@ impl Library { |
| 1105 | keys.get(&identity).map(|(key, _)| key.clone()) | 1105 | keys.get(&identity).map(|(key, _)| key.clone()) |
| 1106 | } | 1106 | } |
| 1107 | 1107 | ||
| 1108 | /// Whether any of its password-protected sections is unlocked, which Lock All would lock. | ||
| 1109 | pub fn any_unlocked(&self) -> bool { | ||
| 1110 | self.keys.0.lock().is_ok_and(|keys| !keys.is_empty()) | ||
| 1111 | } | ||
| 1112 | |||
| 1108 | /// Marks the unlocked section at catalog `path` worked in now. | 1113 | /// Marks the unlocked section at catalog `path` worked in now. |
| 1109 | pub fn touch(&self, path: &str) { | 1114 | pub fn touch(&self, path: &str) { |
| 1110 | if let (Some(identity), Ok(mut keys)) = (self.section_identity(path), self.keys.0.lock()) | 1115 | if let (Some(identity), Ok(mut keys)) = (self.section_identity(path), self.keys.0.lock()) |
crates/snowbound/src/menus.rs+1-1| ... | @@ -587,7 +587,7 @@ impl State { | ... | @@ -587,7 +587,7 @@ impl State { |
| 587 | item( | 587 | item( |
| 588 | Action::RecycleBin, | 588 | Action::RecycleBin, |
| 589 | "Notebook Recycle Bin", | 589 | "Notebook Recycle Bin", |
| 590 | library.catalog().is_none(), | 590 | !self.recycle_bin_available(library), |
| 591 | false, | 591 | false, |
| 592 | ), | 592 | ), |
| 593 | item( | 593 | item( |
crates/snowbound/src/recycle.rs+11| ... | @@ -38,6 +38,17 @@ impl State { | ... | @@ -38,6 +38,17 @@ impl State { |
| 38 | (self.session.as_ref()).is_some_and(|session| binned(&session.tabs[session.tab].path)) | 38 | (self.session.as_ref()).is_some_and(|session| binned(&session.tabs[session.tab].path)) |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | /// Whether Notebook Recycle Bin can do anything on `library`: leave its bin, shown, or | ||
| 42 | /// show one holding sections. | ||
| 43 | pub(crate) fn recycle_bin_available(&self, library: &Arc<Library>) -> bool { | ||
| 44 | let shown = self | ||
| 45 | .session | ||
| 46 | .as_ref() | ||
| 47 | .is_some_and(|session| Arc::ptr_eq(&session.library, library)); | ||
| 48 | library.catalog().is_some() | ||
| 49 | && (shown && self.in_recycle_bin() || !library.tabs(BIN).is_empty()) | ||
| 50 | } | ||
| 51 | |||
| 41 | /// Notebook Recycle Bin: shows `library`'s bin at its first section holding pages, or | 52 | /// Notebook Recycle Bin: shows `library`'s bin at its first section holding pages, or |
| 42 | /// while it shows, goes back to the notebook. | 53 | /// while it shows, goes back to the notebook. |
| 43 | pub(crate) fn toggle_recycle_bin(&mut self, library: Arc<Library>) { | 54 | pub(crate) fn toggle_recycle_bin(&mut self, library: Arc<Library>) { |