Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d424b63
docs: design Origin project import
Limdongcheng Jul 21, 2026
394237a
docs: plan Origin OPJ import
Limdongcheng Jul 21, 2026
5c593d0
chore: ignore local worktrees
Limdongcheng Jul 21, 2026
320e663
test: add licensed Origin project fixtures
Limdongcheng Jul 21, 2026
b7f8a3b
docs: record Origin fixture task completion
Limdongcheng Jul 21, 2026
399d3f1
feat(io): detect Origin project formats
Limdongcheng Jul 21, 2026
c0de4f3
fix(io): bound Origin probe metadata
Limdongcheng Jul 21, 2026
cb38b9f
fix(io): satisfy Origin probe lint gate
Limdongcheng Jul 21, 2026
086fe35
docs: record Origin probe task completion
Limdongcheng Jul 21, 2026
5c6d780
feat(io): add bounded OPJ block reader
Limdongcheng Jul 21, 2026
bce96d8
docs: record OPJ reader task completion
Limdongcheng Jul 21, 2026
e36925d
feat(io): decode supported OPJ worksheet columns
Limdongcheng Jul 21, 2026
2f6fcf0
test(io): cover OPJ record resource limits
Limdongcheng Jul 21, 2026
918a61f
docs: record OPJ record task completion
Limdongcheng Jul 21, 2026
e1800d3
feat(io): import Origin 7.0552 OPJ worksheets
Limdongcheng Jul 21, 2026
389ae87
fix(io): preserve valid Origin column names
Limdongcheng Jul 21, 2026
f4c3dd8
fix(io): bound Origin metadata traversal
Limdongcheng Jul 21, 2026
faeac5b
docs: record OPJ assembly task completion
Limdongcheng Jul 21, 2026
4d0f935
feat(core): convert Origin worksheets to typed tables
Limdongcheng Jul 21, 2026
0e21c69
fix(core): harden Origin table preflight
Limdongcheng Jul 21, 2026
0df0179
docs: record Origin core task completion
Limdongcheng Jul 21, 2026
33cb051
feat(app): add experimental Origin project import
Limdongcheng Jul 22, 2026
f16001a
fix(app): harden Origin import dispatch
Limdongcheng Jul 22, 2026
7479d2e
fix(app): reuse classified Origin file handles
Limdongcheng Jul 22, 2026
05201d0
docs: record Origin app task completion
Limdongcheng Jul 22, 2026
f4379a4
docs: describe experimental Origin import
Limdongcheng Jul 22, 2026
0ba0c72
docs: align table import command label
Limdongcheng Jul 22, 2026
e6861be
docs: clarify Origin omission warnings
Limdongcheng Jul 22, 2026
98b7f22
fix(io): bound OPJ metadata work
Limdongcheng Jul 22, 2026
32ca2b3
docs: preserve OpenOPJ attribution
Limdongcheng Jul 22, 2026
34c33ab
chore: keep worktree ignore local
Limdongcheng Jul 22, 2026
ebe82c5
fix(io): bound raw Origin vector growth
Limdongcheng Jul 22, 2026
609af05
docs: clarify Origin grouping evidence
Limdongcheng Jul 22, 2026
528330a
docs: record Origin verification progress
Limdongcheng Jul 22, 2026
850db39
docs: record final Origin verification
Limdongcheng Jul 22, 2026
12f6568
fix: tighten Origin import boundaries
Limdongcheng Jul 22, 2026
1377797
fix: enforce Origin import resource limits
Limdongcheng Jul 22, 2026
f4d01f8
fix: harden Origin import semantics
Limdongcheng Jul 22, 2026
507e9f9
docs: align Origin source read design
Limdongcheng Jul 22, 2026
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 @@ -73,6 +73,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 @@ -37,6 +37,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]
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 @@ -345,7 +345,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 @@ -324,6 +324,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 @@ -45,7 +45,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 @@ -108,6 +108,34 @@ fn stable_ids_cover_static_and_dynamic_commands() {
assert_eq!(CommandId::RunBatchWorkflow.stable_id(), "tools.automation");
}

#[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
61 changes: 44 additions & 17 deletions crates/app/src/ui/file_dialogs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,39 @@ use plotx_core::state::ProcessingSchemeDialogState;

mod delimited;
mod discovery;
mod origin;
mod path;
mod preview;
mod recent;
mod xlsx;
pub(crate) use delimited::DelimitedTableSource;
use path::{ensure_extension, ensure_plotx_extension, io_error_category};
pub(crate) use preview::table_import_preview_window;
pub(crate) use recent::open_recent_path;
#[cfg(test)]
use recent::{RecentOpenKind, recent_open_kind};
use recent::RecentOpenKind;
pub(crate) use recent::open_recent_path;
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 @@ -290,9 +288,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 @@ -303,7 +326,7 @@ pub(crate) fn commit_table_import_preview(app: &mut PlotxApp) -> bool {
);
}
if let Some(path) = preview.recent_path {
app.note_recent_file(&path);
note_recent_file(app, &path);
}
app.session.record_operation(preview.report);
true
Expand All @@ -320,8 +343,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 (*.abf, *.jdf, fid, ser, *.zip)",
&["abf", "jdf", "fid", "ser", "zip"],
"All supported data (*.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("Axon Binary Format 2 (*.abf)", &["abf"])
.add_filter("JEOL Delta (*.jdf)", &["jdf"])
Expand All @@ -332,7 +359,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