mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-14 19:27:07 -04:00
Merge PR #845
This commit is contained in:
commit
6ceb57d4f5
8 files changed
+2280
-540
No files matched your search
+880
-41
File diff suppressed because it is too large.
Load diff
@@ -15,7 +15,8 @@ mod editorial;
|
||||
mod endnotes;
|
||||
mod export;
|
||||
mod file;
|
||||
mod find;
|
||||
pub(crate) mod find;
|
||||
pub use find::same_find_domain;
|
||||
mod fonts;
|
||||
mod guides;
|
||||
mod index;
|
||||
|
||||
@@ -365,7 +365,8 @@ fn select_at(s: &mut Session, p: &Value, unit: &str) -> Result<Value> {
|
||||
s.typing_format = None;
|
||||
let st = s.doc_mut()?;
|
||||
st.selection = Selection::text(TextSel { story: sid, anchor: a, focus: e, frame: Some(frame), cell });
|
||||
st.revision += 1;
|
||||
// Explicit reselection invalidates the Find scope even when the range is unchanged.
|
||||
st.revision = st.revision.wrapping_add(1);
|
||||
Ok(json!({"story": sid.0, "anchor": a, "focus": e}))
|
||||
}
|
||||
|
||||
|
||||
@@ -260,6 +260,7 @@ pub struct Session {
|
||||
pub cache: Arc<Cache>,
|
||||
pub(crate) tool: Box<dyn designcraft_tools::Tool>,
|
||||
pub journal: Vec<(String, Value)>,
|
||||
pub(crate) find_context: Option<cmd::find::FindContext>,
|
||||
pub clipboard: Option<Arc<Document>>,
|
||||
/// Crash-recovery folder (the app sets it; saving or closing a document removes its entry).
|
||||
pub recovery_dir: Option<std::path::PathBuf>,
|
||||
@@ -302,6 +303,7 @@ impl Session {
|
||||
cache: Arc::new(Cache::new()),
|
||||
tool: designcraft_tools::create("selection"),
|
||||
journal: vec![],
|
||||
find_context: None,
|
||||
clipboard: None,
|
||||
recovery_dir: None,
|
||||
text_clipboard: None,
|
||||
@@ -361,13 +363,26 @@ impl Session {
|
||||
pub fn doc_mut(&mut self) -> Result<&mut DocState> {
|
||||
self.active_mut().ok_or(EngineError::NoDocument)
|
||||
}
|
||||
/// Release the transient Find/Change domain when its caller starts or closes a search.
|
||||
pub fn reset_find_scope(&mut self) {
|
||||
self.find_context = None;
|
||||
}
|
||||
|
||||
fn discard_stale_find_scope(&mut self) {
|
||||
if self.find_context.as_ref().is_some_and(|c| self.active().is_none_or(|st| !c.is_current(st))) {
|
||||
self.reset_find_scope();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_active(&mut self, i: usize) {
|
||||
if i < self.docs.len() && self.active != Some(i) {
|
||||
self.reset_find_scope();
|
||||
self.stop_data_preview();
|
||||
self.active = Some(i);
|
||||
}
|
||||
}
|
||||
pub fn add_document(&mut self, d: DocState) -> usize {
|
||||
self.reset_find_scope();
|
||||
self.stop_data_preview();
|
||||
self.docs.push(d);
|
||||
self.active = Some(self.docs.len() - 1);
|
||||
@@ -391,6 +406,7 @@ impl Session {
|
||||
recovery::discard_document(self, i, recovery_dir.as_deref());
|
||||
}
|
||||
if i < self.docs.len() {
|
||||
self.reset_find_scope();
|
||||
self.docs.remove(i);
|
||||
self.active = if self.docs.is_empty() { None } else { Some(i.min(self.docs.len() - 1)) };
|
||||
}
|
||||
@@ -421,6 +437,8 @@ impl Session {
|
||||
if id != "data.preview" && (spec.undoable || spec.journal) {
|
||||
self.stop_data_preview();
|
||||
}
|
||||
// Preview restoration can replace the document before a Find handler resolves bounds.
|
||||
self.discard_stale_find_scope();
|
||||
let before = self.active().map(|d| (d.uid, d.doc.clone()));
|
||||
let r = (spec.run)(self, params)?;
|
||||
// Undo, redo or deleting layers can take the active layer away: new objects would land on
|
||||
@@ -458,6 +476,7 @@ impl Session {
|
||||
if spec.journal {
|
||||
self.journal.push((id.to_string(), params.clone()));
|
||||
}
|
||||
self.discard_stale_find_scope();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
@@ -545,6 +564,7 @@ impl Session {
|
||||
st.doc = Arc::new(doc);
|
||||
st.selection = sel;
|
||||
st.revision += 1;
|
||||
self.reset_find_scope();
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,13 +310,19 @@ fn handle_request(app: &mut DesignApp, ctx: &egui::Context, req: &ControlRequest
|
||||
"ui.dialog.set" => match app.ui.dialog.as_mut() {
|
||||
Some(d) => {
|
||||
let Some(f) = s("field") else { return err("missing `field`") };
|
||||
d.fields.insert(f.to_string(), p.get("value").cloned().unwrap_or(Value::Null));
|
||||
let value = p.get("value").cloned().unwrap_or(Value::Null);
|
||||
if d.set_field(f, value) {
|
||||
app.session.reset_find_scope();
|
||||
}
|
||||
ok(serde_json::to_value(&*d).unwrap_or_default())
|
||||
}
|
||||
None => err("no dialog open"),
|
||||
},
|
||||
"ui.dialog.open" => {
|
||||
let Some(id) = s("id") else { return err("missing `id`") };
|
||||
if id == "findChange" || app.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange") {
|
||||
app.session.reset_find_scope();
|
||||
}
|
||||
let d = crate::dialogs::Dialog::new(id, p.get("fields").cloned().unwrap_or(Value::Null));
|
||||
let v = serde_json::to_value(&d).unwrap_or_default();
|
||||
app.ui.dialog = Some(d);
|
||||
|
||||
+1342
-495
File diff suppressed because it is too large.
Load diff
@@ -739,6 +739,7 @@ impl DesignApp {
|
||||
pub fn after_engine(&mut self) {
|
||||
let reqs = std::mem::take(&mut self.session.ui_requests);
|
||||
for r in reqs {
|
||||
let was_find = self.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange");
|
||||
match r {
|
||||
UiRequest::Dialog { id, params } => self.ui.dialog = Some(dialogs::Dialog::new(&id, params)),
|
||||
UiRequest::View { params } => canvas::apply_view_request(self, ¶ms),
|
||||
@@ -747,6 +748,10 @@ impl DesignApp {
|
||||
let _ = self.pick_and_open(&purpose);
|
||||
}
|
||||
}
|
||||
// Release only when the request actually replaces Find, including a native PDF picker.
|
||||
if was_find && !self.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange") {
|
||||
self.session.reset_find_scope();
|
||||
}
|
||||
}
|
||||
// New documents get a fitted view.
|
||||
if let Some(st) = self.session.active() {
|
||||
@@ -1106,6 +1111,9 @@ impl DesignApp {
|
||||
let r = self.run(cmd, params);
|
||||
if let Err(e) = &r {
|
||||
let title = if cmd == "file.place" { "Can't Place the File" } else { "Can't Open the File" };
|
||||
if self.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange") {
|
||||
self.session.reset_find_scope();
|
||||
}
|
||||
self.ui.dialog = Some(dialogs::Dialog::new("alert", json!({"title": title, "file": file, "message": e})));
|
||||
} else if let Ok(result) = &r {
|
||||
let warnings: Vec<&str> = result
|
||||
@@ -2248,7 +2256,7 @@ mod browser_import_tests {
|
||||
result.push_str(&format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objects.len() + 1));
|
||||
result.into_bytes()
|
||||
}
|
||||
fn pdf() -> Vec<u8> {
|
||||
pub(super) fn pdf() -> Vec<u8> {
|
||||
pdf_pages(3)
|
||||
}
|
||||
|
||||
|
||||
@@ -758,6 +758,15 @@ pub fn ui_label(id: &str) -> Option<(&'static str, Option<&'static str>)> {
|
||||
|
||||
/// Run a UI command; `None` if `id` isn't one.
|
||||
pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value, String>> {
|
||||
let was_find = app.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange");
|
||||
let result = run_ui_inner(app, id, p);
|
||||
if was_find && app.ui.dialog.as_ref().is_none_or(|d| d.id != "findChange") {
|
||||
app.session.reset_find_scope();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value, String>> {
|
||||
if let Some(result) = crate::panel_docking::command(app, id, p) {
|
||||
return Some(result);
|
||||
}
|
||||
@@ -963,6 +972,7 @@ pub fn run_ui(app: &mut DesignApp, id: &str, p: &Value) -> Option<Result<Value,
|
||||
}
|
||||
}
|
||||
"app.findChange" => {
|
||||
app.session.reset_find_scope();
|
||||
app.ui.dialog = Some(crate::dialogs::Dialog::new("findChange", json!({})));
|
||||
Ok(Value::Null)
|
||||
}
|
||||
@@ -1890,6 +1900,14 @@ pub fn close_document(app: &mut DesignApp, index: Option<usize>) {
|
||||
/// A menu item was chosen. An engine command whose label ends in "…" and that takes parameters
|
||||
/// opens a dialog built from its parameter documentation (see [`crate::dialogs::command_fields`]).
|
||||
pub fn activate(app: &mut DesignApp, id: &str, params: &Value) {
|
||||
let was_find = app.ui.dialog.as_ref().is_some_and(|d| d.id == "findChange");
|
||||
activate_inner(app, id, params);
|
||||
if was_find && app.ui.dialog.as_ref().is_none_or(|d| d.id != "findChange") {
|
||||
app.session.reset_find_scope();
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_inner(app: &mut DesignApp, id: &str, params: &Value) {
|
||||
if params.is_null() && id == "file.close" {
|
||||
close_document(app, None);
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user