diff --git a/crates/ui-egui/src/io.rs b/crates/ui-egui/src/io.rs index ee083d7d44..ea27320b52 100644 --- a/crates/ui-egui/src/io.rs +++ b/crates/ui-egui/src/io.rs @@ -446,6 +446,31 @@ mod tests { assert_eq!(&w[1].1[1..4], b"PNG", "PNG from the given path"); } + /// An option skips the options dialog, but without `path` the save panel still picks the file. + #[test] + fn export_selection_with_an_option_and_no_path_asks_for_the_file_only() { + let (mut app, written) = app(); + app.session.execute("file.new", &json!({"width": 60, "height": 40})).unwrap(); + app.session.execute("shape.rectangle", &json!({"x": 5, "y": 5, "width": 20, "height": 10})).unwrap(); + let asked = Rc::new(RefCell::new(0)); + let count = asked.clone(); + app.services.pick_save = Some(Box::new(move |_: &FilePick| { + *count.borrow_mut() += 1; + Some("/tmp/sel.png".into()) + })); + let r = app.run("document.exportSelection", json!({"scale": 2})).unwrap(); + assert_eq!(*asked.borrow(), 1, "the save panel picks the file"); + assert!(app.ui.dialog.is_none(), "no options dialog follows"); + assert_eq!(r["path"], "/tmp/sel.png"); + assert_eq!(&written.borrow()[0].1[1..4], b"PNG"); + app.run("document.exportSelection", json!({"path": "/tmp/sel2.png", "scale": 2})).unwrap(); + assert_eq!(*asked.borrow(), 1, "a path writes without the save panel"); + let w = written.borrow(); + assert_eq!(w.len(), 2); + assert_eq!(w[0].0, "/tmp/sel.png", "the picked file"); + assert_eq!(w[1].0, "/tmp/sel2.png", "the given path"); + } + #[test] fn written_files_open_as_file_urls() { let url = super::file_url("/tmp/My Art #1.pdf"); diff --git a/docs/control-protocol.md b/docs/control-protocol.md index f08e3a010d..e3e99e67ed 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -123,7 +123,7 @@ the two can still win that race. | Background Save / Export | `app.save`, `app.export {path}`, `file.save`, `file.saveAs`, `file.saveCopy`, `file.exportForScreens {folder, …}` | with the `backgroundSave` / `backgroundExport` preferences on (the default) the desktop app writes the file from a snapshot on a worker thread: the call returns `{path, background: true, status}` at once (`file.exportForScreens`: `{background: true, status}`, and the files are written into the folder from a snapshot taken at the call; an `app.export` given an option the format doesn't read: also `warnings`, naming the option), `ui.inspect` lists the jobs still running in `background` (labels such as `"Saving a.vectorcraft"`), and the document counts as saved (as of the snapshot) once its file is written. Quit and Close wait for them. A job that fails reports the error in the status bar only (`ui.inspect` → `ui.status`), a path outside `--automation-write-root` included. With the preferences off (`prefs.set`), saves return once written. `app.save {path?, wait: true}` and `app.export {path, wait: true}` wait for the jobs already running, write the file before they reply whatever the preferences are set to, and return its result or the error. When the save asks first (the question before replacing a file that opened with losses, or on macOS and Linux the file dialog for an untitled document without `path`), the reply is `{pending}` or `"cancelled"` as before, and the save that runs after the answer uses the preferences. A `wait` that is not `true`, `false` or `null` is refused with "`wait` must be true or false", and nothing is written | | `engine.execute` `file.exportForScreens` | `{}` / `{…document.exportForScreens params}` | without params opens `exportForScreens` on the settings the document last exported with (`document.exportSettings`). While the Artboard tool is in use, the artboards it has selected (its `selected` option, the rows the Artboards panel highlights) are checked instead, and `select` is `range`, or `all` when every artboard is selected. Fields: `tab` (`artboards` \| `assets` \| `layers` \| `selection`; OK reads Export Layer or Export Selection on those), `layers` ([layer id…] checked), `selectionMode` (`one` \| `each`), `name` (the file name pattern of `document.exportForScreens`), `counterStart`, `folders` (`none` \| `rows` \| `format` \| `item` \| `itemFormat`), `exportPreset` (the preset applied last), `__savePreset` (the name typed to save one), `select` (`all` \| `range` \| `full`), `range` ("1-3, 5"), `boards` (a bool per artboard; checking them sets `select` and `range`), `includeBleed`, `folder` (desktop; default the Desktop, else home), `openLocation`, `subfolders`, `prefix`, `preset` (`""` \| `mobile` \| `density`), `formats` (rows `{format, scale: "2x" \| "100w" \| "100h" \| "72ppi", suffix, quality?, folder?}`), `settings` (`{png \| png8 \| jpg \| webp \| gif \| svg \| pdf: {…options}}`, the gear's Format Settings; `__settings` names the format shown). `ui.dialog.confirm` exports (a bad range or no folder keeps it open); on the web OK reads "Download" and the files download (several as one `.zip`). With params it runs `document.exportForScreens`: no `folder` returns the files (or with `zip: true` one zip) as `dataBase64`; with `openLocation` the first file is shown in the file manager. When `warnings` is not empty, an export into a folder or a download shows the first in the status bar as Save As shows a save's notes | | Export for Screens' Assets tab, Asset Export panel | `ui.dialog.set` `tab: "assets"`, `assets: [asset id…]`; `window.panel {panel: "assetExport"}` | on the Assets tab of `exportForScreens` the `assets` field lists the checked asset ids (all of them when it opens); `ui.dialog.confirm` runs `document.exportForScreens {assets}` (nothing checked keeps it open) and OK reads "Export Asset". The Asset Export panel acts through `assets.*` and shares the format rows with the dialog (`assets.settings.set`). The dialog has the format presets menu (☰). In the dialog, `exportPreset` names the preset applied last (`ui.dialog.set` of it applies the preset it names, as picking it does; a name that is not a preset's is an error and leaves the dialog as it was), `__exportPresets` lists the presets, and `__savePreset` holds the name being typed after Save Preset…. The panel menu applies presets through `assets.settings.set {exportPreset}` and holds Save As Preset…, Save Changes, Delete Preset, Import Preset… and Export Preset…. Import and Export use picked files. Its Create Sub-folders and Open Location after Export run `assets.settings.set {subfolders}` and `{openLocation}`; its Format Settings… opens the dialog on the Assets tab with `__settings` set to the first row's format. | -| `engine.execute` `file.exportSelection` | `{multiple?}` | File › Export Selection…: collects the selected objects as one asset (`assets.add`; one per object with `multiple: true`) and opens `exportForScreens` on its Assets tab with just them checked → `{assets}`. `document.exportSelection` with params still exports the selection in one go, without the dialog; with no params or `{}` the save panel picks the file and its format's options dialog follows, as after Export As, and the call returns `null` while the dialog is open (OK returns what `file.exportAs` returns, without `format` or `bounds`). `file.exportAs {path, exportSelection: true, …options}` writes the selection without a dialog | +| `engine.execute` `file.exportSelection` | `{multiple?}` | File › Export Selection…: collects the selected objects as one asset (`assets.add`; one per object with `multiple: true`) and opens `exportForScreens` on its Assets tab with just them checked → `{assets}`. `document.exportSelection` with params still exports the selection in one go, without the options dialog (without `path`, the save panel picks the file first); with no params or `{}` the save panel picks the file and its format's options dialog follows, as after Export As, and the call returns `null` while the dialog is open (OK returns what `file.exportAs` returns, without `format` or `bounds`). `file.exportAs {path, exportSelection: true, …options}` writes the selection without a dialog | | CSS Properties panel | `window.panel {panel: "cssProperties"}`; `engine.execute` `css.copy` / `css.exportFile` | the panel shows `css.selection` of the selection (after Generate CSS, while nothing is selected, `css.generate`); its menu holds the options the commands take (`units`, `position`, `dimensions`, `unnamed`, `rasterize`). `css.copy {scope?, …options}` copies the CSS to the clipboard → `{css, rules}`; `css.exportFile {path?, scope?, …options}` writes `css.export` to `path` (else a picked file; the web downloads it) with the pictures of rasterized art next to it → `{path, rules, images, warnings}`, and the status bar shows `warnings` as Export As shows an export's notes | | `ui.dialog.*` on `importPdf` | `{field: "page" \| "allPages" \| "range" \| "cropTo" \| "password", value}` | opening (`app.open`, `file.open`) a PDF, `.ai` or `.ait` with a password, or one with several pages unless it is a `.ai` or `.ait` with its editing data or carries its VectorCraft document (those open whole), or placing (`file.place` without `page`) any of them with several pages or a password, opens the Import PDF dialog (`mode`: `open` or `place`; `pageCount`). While `__locked` is true, set `password` and `ui.dialog.confirm` to unlock it (a one-page file then opens or is placed). Then set `allPages: false` and `range` ("2-3, 5") to open some pages, or `page` to place one, and `cropTo` (`bounding`, `art`, `crop`, `trim`, `bleed`, `media`); `ui.dialog.confirm` opens or places them (placing keeps the other `file.place` params, such as `link`, `template` and `at`, in `__place`). `document.open {pages, cropTo, password}` and `file.place {page, crop, password}` do the same without the dialog | diff --git a/docs/mcp.md b/docs/mcp.md index df04abfb76..c5a4345bf1 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1127,8 +1127,9 @@ follows as after Export As: Save PDF without its Artboards section, SVG Options options, EPS Options, DXF Options without Export Selected Art Only, Text Export Options with Selection Only on, or an exporter plug-in's parameters. Other formats are written at once. OK writes the selection alone, cropped to it, as `document.exportSelection` writes it, with the chosen options. While a dialog is open the call returns `null`, and -OK returns what `file.exportAs` returns, which has no `format` or `bounds` (Text Export Options: `{path}`). To -write without a dialog, pass `path` or any option. `file.exportAs {path, exportSelection: true, …options}` writes the selection the same way. +OK returns what `file.exportAs` returns, which has no `format` or `bounds` (Text Export Options: `{path}`). Any +option skips the options dialog, but without `path` the save panel still picks the file. To write without a +dialog, pass `path`. `file.exportAs {path, exportSelection: true, …options}` writes the selection the same way. Raster exports default to the document's raster effects resolution. An explicit `scale` sets pixels per point (72 times that value in ppi); `options.ppi` wins over either scale option. CLI `run --export` and