Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

*.ico binary
*.png binary
*.opj binary
*.opju binary
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ muda = { version = "0.19.3", default-features = false }
rfd = "0.17"
zip = { version = "8.6", default-features = false, features = ["deflate"] }
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "tiff"] }
libc = "0.2"
pdf-writer = "0.12"
resvg = "0.47"
svg2pdf = "0.13"
Expand Down
3 changes: 3 additions & 0 deletions crates/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ uuid.workspace = true
raw-window-handle.workspace = true
windows-sys.workspace = true

[target.'cfg(unix)'.dependencies]
libc.workspace = true

[target.'cfg(target_os = "macos")'.dependencies]
fontdb.workspace = true
muda.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/app/src/ui/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ fn welcome_start(app: &mut PlotxApp, ui: &mut Ui) {
if welcome_action(ui, icon::FOLDER_OPEN, "Open project…") {
crate::ui::file_dialogs::open_project(app);
}
if welcome_action(ui, icon::TABLE, "Import table / CSV…") {
if welcome_action(ui, icon::TABLE, "Import table…") {
crate::ui::file_dialogs::import_delimited_table(app);
}
if welcome_action(ui, icon::FILE_PLUS, "New empty data table") {
Expand Down
4 changes: 4 additions & 0 deletions crates/app/src/ui/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,10 @@ pub fn describe(app: &PlotxApp, id: CommandId) -> CommandDescriptor {
!app.session.recent_files.is_empty(),
"Open a file or project to build the recent list.",
),
CommandId::ImportTable => requires(
app.session.ui.table_import_preview.is_none(),
"Finish or cancel the current table import preview before importing another table.",
),
CommandId::ExportData => requires(
dataset().is_some_and(|dataset| {
!plotx_core::data_export::DataExportAvailability::for_dataset(dataset).is_empty()
Expand Down
2 changes: 1 addition & 1 deletion crates/app/src/ui/commands/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub(super) fn command_identity(
),
CommandId::ClearRecentFiles => plain("Clear Recent Files", None),
CommandId::HelpManual => plain("User Manual", Some(icon::BOOK_OPEN)),
CommandId::ImportTable => plain("Import Table / CSV…", Some(icon::TABLE)),
CommandId::ImportTable => plain("Import Table…", Some(icon::TABLE)),
CommandId::PasteTable => plain("Paste Table from Clipboard", Some(icon::CLIPBOARD_TEXT)),
CommandId::SaveProject => plain("Save Project", Some(icon::FLOPPY_DISK)),
CommandId::NewTable => plain("New Empty Data Table", Some(icon::TABLE)),
Expand Down
28 changes: 28 additions & 0 deletions crates/app/src/ui/commands_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,34 @@ fn spacing_commands_are_registered_checked_and_execute() {
);
}

#[test]
fn origin_import_reuses_import_table_command_identity() {
let app = app();
assert_eq!(CommandId::ImportTable.stable_id(), "file.import_table");
assert_eq!(
describe(&app, CommandId::ImportTable).label,
"Import Table…"
);
}

#[test]
fn import_table_is_disabled_while_a_table_preview_is_pending() {
let mut app = app();
crate::ui::file_dialogs::import_delimited_text(
&mut app,
"x,y\n0,1\n",
crate::ui::file_dialogs::DelimitedTableSource::Clipboard,
);
assert!(app.session.ui.table_import_preview.is_some());

let command = describe(&app, CommandId::ImportTable);
assert!(!command.enabled);
assert_eq!(
command.disabled_reason,
Some("Finish or cancel the current table import preview before importing another table.")
);
}

#[test]
fn automation_is_a_global_menu_and_palette_command() {
let app = app();
Expand Down
57 changes: 42 additions & 15 deletions crates/app/src/ui/file_dialogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use plotx_core::state::ProcessingSchemeDialogState;

mod delimited;
mod discovery;
mod origin;
mod path;
mod preview;
mod recent;
Expand All @@ -21,26 +22,23 @@ use xlsx::import_xlsx_table_path;
pub(crate) fn import_delimited_table(app: &mut PlotxApp) {
let Some(path) = rfd::FileDialog::new()
.add_filter(
"Table (*.csv, *.tsv, *.txt, *.xlsx)",
&["csv", "tsv", "txt", "xlsx"],
"Table (*.csv, *.tsv, *.txt, *.xlsx, *.opj)",
origin::IMPORT_TABLE_FILTER_EXTENSIONS,
)
.add_filter(
origin::ORIGIN_PROJECT_FILTER_LABEL,
origin::ORIGIN_PROJECT_FILTER_EXTENSIONS,
)
.add_filter("Excel workbook (*.xlsx)", &["xlsx"])
.add_filter("CSV (*.csv)", &["csv"])
.add_filter("TSV (*.tsv)", &["tsv"])
.add_filter("All files", &["*"])
.set_title("Import a comma, tab, or semicolon delimited table")
.set_title("Import a table")
.pick_file()
else {
return;
};
if path
.extension()
.is_some_and(|extension| extension.eq_ignore_ascii_case("xlsx"))
{
import_xlsx_table_path(app, &path);
} else {
import_delimited_table_path(app, &path);
}
open_recent_path(app, &path);
}

fn import_delimited_table_path(app: &mut PlotxApp, path: &std::path::Path) {
Expand Down Expand Up @@ -288,9 +286,34 @@ pub(crate) fn import_delimited_text_with_schema(
}

pub(crate) fn commit_table_import_preview(app: &mut PlotxApp) -> bool {
commit_table_import_preview_with_recent(app, PlotxApp::note_recent_file)
}

pub(crate) fn commit_table_import_preview_with_recent<F>(
app: &mut PlotxApp,
mut note_recent_file: F,
) -> bool
where
F: FnMut(&mut PlotxApp, &std::path::Path),
{
let Some(preview) = app.session.ui.table_import_preview.take() else {
return false;
};
if preview.candidates.is_empty() {
app.session.record_operation(OperationReport::<()>::failure(
preview.report.id,
OperationKind::TableImport,
"Table import failed because there are no supported tables to import.",
Diagnostic::new(
Severity::Error,
DiagnosticCode::TableImportFailed,
"The import preview contains no supported table candidates.",
)
.with_source("app.table_import")
.with_context("stage", "preview_commit"),
));
return false;
}
for candidate in preview.candidates {
app.import_table_dataset_typed(
candidate.name,
Expand All @@ -305,7 +328,7 @@ pub(crate) fn commit_table_import_preview(app: &mut PlotxApp) -> bool {
// through the status line. Recording the import first keeps that
// diagnostic: `record_operation` replaces the status unconditionally.
if let Some(path) = preview.recent_path {
app.note_recent_file(&path);
note_recent_file(app, &path);
}
true
}
Expand All @@ -321,8 +344,12 @@ pub(crate) fn load_and_note(app: &mut PlotxApp, path: &std::path::Path) {
pub(crate) fn open_file(app: &mut PlotxApp) {
if let Some(paths) = rfd::FileDialog::new()
.add_filter(
"All supported data (*.spm, *.pfc, *.abf, *.jdf, fid, ser, *.zip)",
&["spm", "pfc", "abf", "jdf", "fid", "ser", "zip"],
"All supported data (*.spm, *.pfc, *.abf, *.jdf, fid, ser, *.zip, *.opj)",
origin::OPEN_FILE_FILTER_EXTENSIONS,
)
.add_filter(
origin::ORIGIN_PROJECT_FILTER_LABEL,
origin::ORIGIN_PROJECT_FILTER_EXTENSIONS,
)
.add_filter("Bruker NanoScope AFM (*.spm, *.pfc)", &["spm", "pfc"])
.add_filter("Axon Binary Format 2 (*.abf)", &["abf"])
Expand All @@ -334,7 +361,7 @@ pub(crate) fn open_file(app: &mut PlotxApp) {
.pick_files()
{
for path in paths {
load_and_note(app, &path);
open_recent_path(app, &path);
}
}
}
Expand Down
Loading
Loading