Skip to content

Commit 3d424ee

Browse files
authored
feat(app): add background project lifecycle saves (#25)
Run manual project saves and recovery checkpoints from captured snapshots without blocking the UI. Unify new, open, close, and quit transitions behind Save / Discard / Cancel, and stream large electrophysiology and AFM payloads in bounded chunks.
1 parent a03b6e2 commit 3d424ee

49 files changed

Lines changed: 1042 additions & 261 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/app/src/main.rs

Lines changed: 269 additions & 12 deletions
Large diffs are not rendered by default.

crates/app/src/shot.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ mod tests {
412412
#[test]
413413
fn automated_exit_bypasses_dirty_project_prompt() {
414414
let mut app = PlotxApp::new_with_settings(plotx_core::settings::Settings::default());
415-
app.doc.dirty = true;
415+
app.mark_document_dirty();
416416
let ctx = egui::Context::default();
417417

418418
let output = ctx.run_ui(egui::RawInput::default(), |ui| {

crates/app/src/ui/canvas/board_notes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ pub(crate) fn render_inline_panel_note_editor(app: &mut PlotxApp, screen: egui::
136136
.and_then(|object| object.plot_mut())
137137
{
138138
plot.panel.user_note = buffer.clone();
139-
app.doc.dirty = true;
139+
app.mark_document_dirty();
140140
}
141141
}
142142

crates/app/src/ui/canvas/navigation.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ pub(crate) fn apply_plot_pan(
275275
.clamp_to(plot_object.viewport.full_y);
276276
plot_object.viewport.auto_y = false;
277277
plot_object.apply_viewport();
278-
app.doc.dirty = true;
278+
app.mark_document_dirty();
279279
}
280280

281281
pub(crate) fn commit_data_pan(app: &mut PlotxApp) {
@@ -650,7 +650,7 @@ pub(crate) fn zoom_plot_viewport(
650650
plot_object.viewport.zoom_y(anchor, scale);
651651
}
652652
plot_object.apply_viewport();
653-
app.doc.dirty = true;
653+
app.mark_document_dirty();
654654
ui.ctx()
655655
.request_repaint_after(std::time::Duration::from_millis(200));
656656
}

crates/app/src/ui/canvas/panel_notes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ pub(crate) fn handle_panel_label_interactions(
9898
(drag.before.position[0] + delta.x).clamp(0.0, max_x),
9999
(drag.before.position[1] + delta.y).clamp(0.0, max_y),
100100
];
101-
app.doc.dirty = true;
101+
app.mark_document_dirty();
102102
}
103103
}
104104
if primary_released || !primary_down {

crates/app/src/ui/canvas/phase.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ pub(crate) fn handle_phase_drag(
196196
if let Some(ppm) = preview
197197
&& app.doc.datasets[di].repivot_ppm(axis, ppm)
198198
{
199-
app.doc.dirty = true;
199+
app.mark_document_dirty();
200200
}
201201
finish_phase_drag(app);
202202
}

crates/app/src/ui/command_exec.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
//! descriptions makes the live catalog easy to inspect and keeps source files
33
//! within the repository size limit.
44
5-
use plotx_core::state::{CommandPaletteState, LineShapeKind, PlotxApp, Tool, ToolGroup};
5+
use plotx_core::state::{
6+
CommandPaletteState, LineShapeKind, PlotxApp, ProjectTransition, Tool, ToolGroup,
7+
};
68

79
use super::clipboard_table::ClipboardTablePaste;
810
use super::commands::{self, CommandId};
@@ -26,7 +28,13 @@ pub fn execute(
2628
return;
2729
}
2830
match id {
31+
CommandId::NewProject => {
32+
app.request_project_transition(ProjectTransition::New);
33+
}
2934
CommandId::OpenProject => super::file_dialogs::open_project(app),
35+
CommandId::CloseProject => {
36+
app.request_project_transition(ProjectTransition::Close);
37+
}
3038
CommandId::OpenFile => super::file_dialogs::open_file(app),
3139
CommandId::OpenFolder => super::file_dialogs::open_folder(app),
3240
CommandId::RunBatchWorkflow => super::batch_workflow::AutomationUi::request_open(ctx),

crates/app/src/ui/commands.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ pub enum Applicability {
3737

3838
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3939
pub enum CommandId {
40+
NewProject,
4041
OpenProject,
42+
CloseProject,
4143
OpenFile,
4244
OpenFolder,
4345
RunBatchWorkflow,
@@ -175,7 +177,9 @@ pub struct CommandDescriptor {
175177

176178
pub fn catalog(app: &PlotxApp) -> Vec<CommandDescriptor> {
177179
let mut ids = vec![
180+
CommandId::NewProject,
178181
CommandId::OpenProject,
182+
CommandId::CloseProject,
179183
CommandId::OpenFile,
180184
CommandId::OpenFolder,
181185
CommandId::RunBatchWorkflow,
@@ -368,6 +372,18 @@ pub fn describe(app: &PlotxApp, id: CommandId) -> CommandDescriptor {
368372
// command can never be blocked by one requirement while explaining another.
369373
// `and_then` reports the first unmet requirement and skips the rest.
370374
let gate: Result<(), &'static str> = match id {
375+
CommandId::CloseProject => requires(
376+
app.session.project_present
377+
|| app.doc.project_path.is_some()
378+
|| !app.doc.datasets.is_empty()
379+
|| !app.doc.canvases.is_empty()
380+
|| app.doc.dirty,
381+
"There is no project to close.",
382+
),
383+
CommandId::SaveProject => requires(
384+
!app.session.ui.project_save_in_progress,
385+
"Wait for the current project save to finish.",
386+
),
371387
CommandId::OpenRecent(index) => requires(
372388
index < app.session.recent_files.len(),
373389
"Open a file or project to fill the recent list.",

crates/app/src/ui/commands/identity.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ pub(super) fn command_identity(
3939
) -> (String, Option<&'static str>, Option<bool>) {
4040
let plain = |text: &str, glyph| (text.to_owned(), glyph, None);
4141
match id {
42+
CommandId::NewProject => plain("New Project", Some(icon::FILE_PLUS)),
4243
CommandId::OpenProject => plain("Open Project…", Some(icon::FOLDER_OPEN)),
44+
CommandId::CloseProject => plain("Close Project", Some(icon::X)),
4345
CommandId::OpenFile => plain("Open File…", Some(icon::FILE)),
4446
CommandId::OpenFolder => plain("Open Folder…", Some(icon::FOLDER)),
4547
CommandId::RunBatchWorkflow => plain("Automation…", Some(icon::PLAY)),
@@ -329,7 +331,9 @@ fn recent_label(app: &PlotxApp, index: usize) -> String {
329331

330332
fn simple_stable_id(id: CommandId) -> &'static str {
331333
match id {
334+
CommandId::NewProject => "file.new_project",
332335
CommandId::OpenProject => "file.open_project",
336+
CommandId::CloseProject => "file.close_project",
333337
CommandId::OpenFile => "file.open_file",
334338
CommandId::OpenFolder => "file.open_folder",
335339
CommandId::RunBatchWorkflow => "tools.automation",

crates/app/src/ui/commands_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ fn ribbon_groups(app: &PlotxApp) -> Vec<(WorkflowTab, Vec<&'static str>)> {
147147

148148
#[test]
149149
fn stable_ids_cover_static_and_dynamic_commands() {
150+
assert_eq!(CommandId::NewProject.stable_id(), "file.new_project");
151+
assert_eq!(CommandId::CloseProject.stable_id(), "file.close_project");
150152
assert_eq!(CommandId::SaveProject.stable_id(), "file.save");
151153
assert_eq!(CommandId::ArrangeGrid(2, 3).stable_id(), "arrange.grid.2x3");
152154
assert_eq!(CommandId::Tool(Tool::LineFit).stable_id(), "tool.line_fit");

0 commit comments

Comments
 (0)