Export: a progress dialog with Cancel for background exports

While an export runs in the background (Background Export), a dialog shows
what is being exported and how far it has got: artboard by artboard for a
raster export of several artboards, busy otherwise. Cancel (also Esc) stops
it and writes no file; Continue in Background leaves it to the status bar.
It opens after 0.4 s and never over another dialog.

Built on the shared vectorcraft_doc::cancel (#2546): fileio::progress::Progress
wraps its CancelToken and a progress sink, so a cancelled export stops
drawing and keeps no half-drawn pattern tile or effect raster.

- ui: background jobs carry their Progress; export_progress draws the
  dialog; UI commands file.cancelExport and file.exportInBackground.
- i18n: the strings in every language with complete menus, Danish, Dutch
  and Indonesian included; "{n} more exports waiting" is a plural (trn).
- docs: ui-parity, gaps (G12) and ROADMAP rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dt49z1ubK9x1bPq51APgz3
This commit is contained in:
Fabian Grajales
2026-10-18 12:40:20 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent a275496ef4
commit ec410552cf
34 changed files with 560 additions and 15 deletions
+3 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -40,6 +40,7 @@ pub mod pdf;
mod pdfimport;
pub mod pngtext;
pub mod ppi;
pub mod progress;
pub mod psdread;
mod save;
mod screens;
+82
View File
@@ -0,0 +1,82 @@
//! Progress and Cancel of an export running off the UI thread (Background Export), on the shared
//! [`vectorcraft_doc::cancel`] mechanism.
//!
//! The frontend makes a [`Progress`], runs the export inside [`scope`] on its worker and reads the
//! counts from the UI thread to draw them; its Cancel button calls [`Progress::cancel`]. Inside the
//! scope the encoders that work in steps (raster formats: one artboard at a time) report each step
//! through [`vectorcraft_doc::cancel::progress`] and stop at the next check once cancelled; the
//! renderer stops drawing and keeps no half-drawn pattern tile or effect raster. Encoders that
//! make one file in one go report nothing: the frontend shows them as busy and checks
//! [`Progress::cancelled`] itself before writing, so a cancelled export never writes a file.
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use vectorcraft_doc::cancel::{self, CancelToken, ProgressSink};
/// How far an export has got, shared between the worker and the UI thread.
#[derive(Debug, Default)]
pub struct Progress {
token: CancelToken,
done: AtomicU64,
total: AtomicU64,
}
impl Progress {
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
/// `(steps done, steps in all)`; `total` is 0 while the export can't say (one file made in one go).
pub fn counts(&self) -> (u64, u64) {
let total = self.total.load(Ordering::Relaxed);
(self.done.load(Ordering::Relaxed).min(total), total)
}
/// The share of the export done, 0 to 1; `None` while the export can't say.
pub fn fraction(&self) -> Option<f64> {
let (done, total) = self.counts();
(total > 0).then(|| done as f64 / total as f64)
}
/// Ask the export to stop: it writes nothing more, and a render under way stops drawing.
pub fn cancel(&self) {
self.token.cancel();
}
pub fn cancelled(&self) -> bool {
self.token.is_cancelled()
}
}
/// Run `f` with `progress`'s token in effect on this thread and the steps reported to it.
pub fn scope<R>(progress: &Arc<Progress>, f: impl FnOnce() -> R) -> R {
let to = Arc::clone(progress);
let sink: ProgressSink = Rc::new(move |done, total, _| {
to.total.store(total, Ordering::Relaxed);
to.done.store(done, Ordering::Relaxed);
});
cancel::scope(&progress.token, Some(sink), f)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_raster_export_counts_its_artboards_and_stops_once_cancelled() {
let mut doc = vectorcraft_doc::Document::new(100.0, 100.0);
let second = doc.artboards[0].clone();
doc.artboards.push(second);
let p = Progress::new();
assert_eq!(p.fraction(), None, "nothing reported yet");
let png = scope(&p, || super::super::encode_all(&doc, "png", &serde_json::json!({"useArtboards": true})));
assert!(png.is_ok(), "{:?}", png.err());
assert_eq!(p.counts(), (1, 2), "the step under way when the last artboard started");
p.cancel();
let r = scope(&p, || super::super::encode_all(&doc, "png", &serde_json::json!({"useArtboards": true})));
assert!(matches!(r, Err(crate::EngineError::Cancelled)), "{:?}", r.err());
assert!(!cancel::is_cancelled(), "the scope ended");
}
}
+52 -1
View File
@@ -6,9 +6,11 @@
//! It needs a thread-safe writer from the host ([`crate::Services::write_shared`], desktop);
//! without one (the web, which downloads) the work runs at once, as with the preference off.
use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender, channel};
use serde_json::{Value, json};
use vectorcraft_engine::cmd::fileio::progress::Progress;
use vectorcraft_engine::file_access::ScopeHandle;
use crate::VectorcraftApp;
@@ -50,9 +52,24 @@ pub struct Job {
pub label: String,
/// The document the job reads (`DocState::uid`), if any.
pub doc: Option<u64>,
/// How far an export has got, and its Cancel ([`crate::export_progress`] shows it).
pub progress: Option<Arc<Progress>>,
/// Continue in Background: the progress dialog no longer shows this job.
pub hidden: bool,
then: Then,
}
impl Job {
pub fn id(&self) -> u64 {
self.id
}
/// Was the job cancelled (it writes nothing more; its result is still on its way)?
pub fn cancelled(&self) -> bool {
self.progress.as_ref().is_some_and(|p| p.cancelled())
}
}
/// The worker and the jobs it has not finished.
#[derive(Default)]
pub struct Background {
@@ -93,6 +110,26 @@ impl Background {
pub fn busy_with(&self, uid: u64) -> bool {
self.jobs.iter().any(|j| j.doc == Some(uid))
}
/// Cancel every export in progress → how many there were.
pub fn cancel_exports(&mut self) -> usize {
let mut n = 0;
for p in self.jobs.iter().filter(|j| !j.cancelled()).filter_map(|j| j.progress.as_ref()) {
p.cancel();
n += 1;
}
n
}
/// Hide the exports in progress from the progress dialog (they go on) → how many there were.
pub fn hide_exports(&mut self) -> usize {
let mut n = 0;
for j in self.jobs.iter_mut().filter(|j| j.progress.is_some() && !j.hidden && !j.cancelled()) {
j.hidden = true;
n += 1;
}
n
}
}
/// Run `work` on a worker thread when `enabled` and the host can write from one, else now; `then`
@@ -106,6 +143,20 @@ pub fn run(
doc: Option<u64>,
work: impl FnOnce(Writer) -> Result<Value, String> + Send + 'static,
then: impl FnOnce(&mut VectorcraftApp, Result<Value, String>) -> Result<Value, String> + 'static,
) -> Result<Value, String> {
run_with_progress(app, enabled, label, doc, None, work, then)
}
/// [`run`] for work that reports to `progress` (an export): the progress dialog shows it while it
/// runs in the background, with Cancel.
pub fn run_with_progress(
app: &mut VectorcraftApp,
enabled: bool,
label: String,
doc: Option<u64>,
progress: Option<Arc<Progress>>,
work: impl FnOnce(Writer) -> Result<Value, String> + Send + 'static,
then: impl FnOnce(&mut VectorcraftApp, Result<Value, String>) -> Result<Value, String> + 'static,
) -> Result<Value, String> {
let mut work: Work = Box::new(work);
let shared = app.services.write_shared.clone().filter(|_| enabled && app.services.download.is_none());
@@ -116,7 +167,7 @@ pub fn run(
Ok(()) => {
let bg = &mut app.background;
bg.next_id += 1;
bg.jobs.push(Job { id, label: label.clone(), doc, then: Box::new(then) });
bg.jobs.push(Job { id, label: label.clone(), doc, progress, hidden: false, then: Box::new(then) });
if label.is_empty() {
return Ok(json!({ "background": true }));
}
+1 -1
View File
@@ -739,7 +739,7 @@ pub fn status_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
.background
.jobs
.iter()
.filter(|j| !j.label.is_empty())
.filter(|j| !j.label.is_empty() && !j.cancelled())
.map(|j| j.label.as_str())
.chain(app.opening.pending.iter().map(|p| p.label.as_str()))
.collect();
+196
View File
@@ -0,0 +1,196 @@
//! The export progress dialog: while an export runs in the background (Background Export) a modal
//! window shows what is being exported, how far it has got (artboard by artboard for raster
//! exports of several artboards; busy for one artboard and for formats made in one go, which
//! report no steps) and two buttons: Cancel (`file.cancelExport`, also
//! Esc) stops it and writes nothing, Continue in Background (`file.exportInBackground`) closes the
//! dialog and leaves the export to the status bar.
//!
//! It opens after [`DELAY`] so a quick export doesn't flash it, and waits while another dialog is
//! open (the overwrite question, an options dialog): never two modals at once.
use serde_json::json;
use crate::dialogs::modal;
use crate::theme::Tokens;
use crate::{VectorcraftApp, widgets};
/// Seconds an export runs before the dialog opens.
pub const DELAY: f64 = 0.4;
/// Where the job a frame first saw is remembered (its id, the time it was first seen).
fn seen_id() -> egui::Id {
egui::Id::new("export-progress-seen")
}
/// What the dialog shows: (label, steps done, steps in all, share done, exports after this one).
fn shown(app: &VectorcraftApp, now: f64, ctx: &egui::Context) -> Option<(String, u64, u64, f32, usize)> {
let jobs: Vec<_> = app.background.jobs.iter().filter(|j| !j.hidden && !j.cancelled()).filter(|j| j.progress.is_some()).collect();
let first = jobs.first()?;
let since = ctx.data_mut(|m| {
let seen = m.get_temp_mut_or_insert_with(seen_id(), || (first.id(), now));
if seen.0 != first.id() {
*seen = (first.id(), now);
}
seen.1
});
if now - since < DELAY {
return None;
}
let (done, total) = first.progress.as_ref().map(|p| p.counts()).unwrap_or_default();
let frac = first.progress.as_ref().and_then(|p| p.fraction()).unwrap_or(0.0) as f32;
Some((first.label.clone(), done, total, frac, jobs.len() - 1))
}
pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
let now = ctx.input(|i| i.time);
let Some((label, done, total, frac, more)) = shown(app, now, ctx) else { return };
if app.ui.dialog.is_some() {
return;
}
let t = Tokens::get(ctx);
let heading = format!("{}…", crate::i18n::msg(&label));
let mut action = None;
modal::show(ctx, &heading, egui::Id::new("export-progress"), -40.0, 20, |ui| {
ui.set_width(360.0);
modal::heading(ui, &heading);
ui.add_space(12.0);
// One step says nothing a spinner doesn't: the bar would sit at 0 % until the file is written.
if total > 1 {
let step = crate::i18n::fmt(tl!("Artboard {n} of {total}"), &[("n", &(done + 1).min(total).to_string()), ("total", &total.to_string())]);
// The count on its own line: drawn over the bar it would straddle the fill's edge.
ui.horizontal(|ui| {
ui.label(egui::RichText::new(step).color(t.text_dim));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(egui::RichText::new(format!("{:.0} %", frac * 100.0)).color(t.text_dim));
});
});
ui.add_space(4.0);
ui.add(egui::ProgressBar::new(frac).desired_width(360.0).desired_height(6.0).fill(t.accent));
} else {
ui.horizontal(|ui| {
ui.add(egui::Spinner::new().size(14.0).color(t.accent));
ui.label(egui::RichText::new(tl!("Encoding and writing the file…")).color(t.text_dim));
});
}
if more > 0 {
ui.add_space(6.0);
let queued = crate::i18n::tn(more as u64, "{n} more export waiting", "{n} more exports waiting");
ui.label(egui::RichText::new(queued).size(11.5).color(t.text_dim));
}
ui.add_space(16.0);
let row = egui::vec2(ui.min_rect().width(), ui.spacing().interact_size.y);
ui.allocate_ui_with_layout(row, egui::Layout::right_to_left(egui::Align::Center), |ui| {
if widgets::secondary_button(ui, "Cancel").clicked() {
action = Some("file.cancelExport");
}
ui.add_space(8.0);
if widgets::secondary_button(ui, "Continue in Background").clicked() {
action = Some("file.exportInBackground");
}
});
});
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
action = Some("file.cancelExport");
}
if let Some(id) = action
&& let Err(e) = app.run(id, json!({}))
{
app.status(e);
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use serde_json::json;
use vectorcraft_engine::Session;
use vectorcraft_engine::cmd::fileio::progress::Progress;
use crate::{Services, VectorcraftApp, background};
/// An app whose background writer waits until `go` is set.
fn app(go: Arc<std::sync::atomic::AtomicBool>) -> VectorcraftApp {
let services = Services {
write_shared: Some(Arc::new(move |_: &str, _: &[u8]| {
while !go.load(std::sync::atomic::Ordering::SeqCst) {
std::thread::sleep(std::time::Duration::from_millis(1));
}
Ok(())
})),
..Default::default()
};
VectorcraftApp::new(Session::new(), services)
}
/// Draws one frame of the dialog at `time` seconds → the texts it drew.
fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, time: f64) -> String {
let mut out = ctx.run_ui(egui::RawInput { time: Some(time), ..Default::default() }, |ui| super::show(app, ui.ctx()));
out.textures_delta.clear();
let mut texts = String::new();
for shape in out.shapes {
if let egui::epaint::Shape::Text(t) = shape.shape {
texts.push_str(t.galley.text());
texts.push('\n');
}
}
texts
}
#[test]
fn opens_after_the_delay_and_cancel_closes_it() {
let go = Arc::new(std::sync::atomic::AtomicBool::new(false));
let mut app = app(go.clone());
let p = Progress::new();
let r = background::run_with_progress(
&mut app,
true,
"Exporting a.png".into(),
None,
Some(p.clone()),
|w| w("/a.png", b"x").map(|()| json!({})),
|_, r| r,
);
assert_eq!(r.unwrap()["background"], true);
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
assert!(!frame(&mut app, &ctx, 0.0).contains("Exporting a.png"), "not before the delay");
frame(&mut app, &ctx, 1.0); // a new window measures itself unseen first
let shown = frame(&mut app, &ctx, 1.1);
assert!(shown.contains("Exporting a.png") && shown.contains("Cancel") && shown.contains("Continue in Background"), "{shown}");
app.run("file.cancelExport", json!({})).unwrap();
assert!(p.cancelled());
assert_eq!(app.ui.status, crate::io::EXPORT_CANCELLED);
assert!(!frame(&mut app, &ctx, 2.0).contains("Exporting a.png"), "a cancelled export isn't shown");
go.store(true, std::sync::atomic::Ordering::SeqCst);
background::wait_all(&mut app);
}
#[test]
fn continue_in_background_hides_it_and_the_export_goes_on() {
let go = Arc::new(std::sync::atomic::AtomicBool::new(false));
let mut app = app(go.clone());
let p = Progress::new();
background::run_with_progress(
&mut app,
true,
"Exporting b.png".into(),
None,
Some(p.clone()),
|w| w("/b.png", b"x").map(|()| json!({})),
|_, r| r,
)
.unwrap();
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
frame(&mut app, &ctx, 0.0);
frame(&mut app, &ctx, 1.0);
assert!(frame(&mut app, &ctx, 1.1).contains("Exporting b.png"));
assert_eq!(app.run("file.exportInBackground", json!({})).unwrap()["hidden"], 1);
assert!(!frame(&mut app, &ctx, 2.0).contains("Exporting b.png"));
assert!(!p.cancelled());
go.store(true, std::sync::atomic::Ordering::SeqCst);
background::wait_all(&mut app);
assert!(app.background.jobs.is_empty());
}
}
+6
View File
@@ -5153,6 +5153,12 @@ theme Appearance Външен вид
Enable Guides for 9-Slice Scaling Включване на водачи за 9-slice мащабиране
Persistent Постоянна
Library Panel Options Опции на панела с библиотека
Artboard {n} of {total} Работна област {n} от {total}
Encoding and writing the file… Кодиране и записване на файла…
@plural {n} more export waiting|{n} more exports waiting Още {n} експортиране в изчакване|Още {n} експортирания в изчакване
Continue in Background Продължаване във фонов режим
Cancel Export Отказ от експортирането
@msg Export cancelled Експортирането е отказано
# Paragraph panel menu › Justification…
Desired Желано
Word Spacing: Разстояние между думите:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5188,6 +5188,12 @@ mark-style Japanese Japonské
@msg too many library panels were opened in this session v této relaci bylo otevřeno příliš mnoho panelů knihoven
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Obraz má {_1} × {_2} pixelů, na jedné straně více než {LARGE_TRACE_SIDE}: vektorizuje se, ale pomaleji. Rychleji se vektorizuje s předvolbou nižší věrnosti nebo menší kopie obrazu.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Obraz byl vektorizován v rozlišení {_1} × {_2} pixelů, zmenšený z {_3} × {_4} (nejvýše {_5} megapixelů).
Artboard {n} of {total} Pracovní plocha {n} z {total}
Encoding and writing the file… Kódování a zápis souboru…
@plural {n} more export waiting|{n} more exports waiting Čeká ještě {n} export|Čekají ještě {n} exporty|Čeká ještě {n} exportů
Continue in Background Pokračovat na pozadí
Cancel Export Zrušit export
@msg Export cancelled Export byl zrušen
# Paragraph panel menu › Justification…
Desired Požadované
Word Spacing: Mezery mezi slovy:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5172,6 +5172,12 @@ swatch Bright Violet Kraftig violet
Typing Indtastning
Persistent Vedvarende
Library Panel Options Indstillinger for bibliotekspanel
Artboard {n} of {total} Tegneflade {n} af {total}
Encoding and writing the file… Koder og skriver filen…
@plural {n} more export waiting|{n} more exports waiting {n} eksport mere venter|{n} eksporter mere venter
Continue in Background Fortsæt i baggrunden
Cancel Export Annullér eksport
@msg Export cancelled Eksport annulleret
# Paragraph panel menu › Justification…
Desired Ønsket
Word Spacing: Ordafstand:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4763,6 +4763,12 @@ effect Regular Regelmäßig
@msg too many library panels were opened in this session in dieser Sitzung wurden zu viele Bibliotheksbedienfelder geöffnet
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Das Bild hat {_1} × {_2} Pixel, mehr als {LARGE_TRACE_SIDE} auf einer Seite: Es wird nachgezeichnet, aber langsamer. Eine Vorgabe mit geringerer Detailtreue oder eine kleinere Kopie des Bildes geht schneller.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Das Bild wurde mit {_1} × {_2} Pixeln nachgezeichnet, verkleinert von {_3} × {_4} (höchstens {_5} Megapixel).
Artboard {n} of {total} Zeichenfläche {n} von {total}
Encoding and writing the file… Datei wird codiert und geschrieben…
@plural {n} more export waiting|{n} more exports waiting {n} weiterer Export wartet|{n} weitere Exporte warten
Continue in Background Im Hintergrund fortsetzen
Cancel Export Export abbrechen
@msg Export cancelled Export abgebrochen
# Paragraph panel menu › Justification…
Desired Optimal
Word Spacing: Wortabstand:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5140,6 +5140,12 @@ effect Points Σημεία
@msg too many library panels were opened in this session άνοιξαν πάρα πολλοί πίνακες βιβλιοθηκών σε αυτή τη συνεδρία
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Η εικόνα είναι {_1} × {_2} εικονοστοιχεία, πάνω από {LARGE_TRACE_SIDE} σε μία πλευρά: ανιχνεύεται, αλλά πιο αργά. Μια προρύθμιση χαμηλότερης πιστότητας ή ένα μικρότερο αντίγραφο της εικόνας ανιχνεύεται γρηγορότερα.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Η εικόνα ανιχνεύτηκε σε {_1} × {_2} εικονοστοιχεία, σμικρυμένη από {_3} × {_4} (το πολύ {_5} megapixel).
Artboard {n} of {total} Πίνακας σχεδίασης {n} από {total}
Encoding and writing the file… Κωδικοποίηση και εγγραφή του αρχείου…
@plural {n} more export waiting|{n} more exports waiting {n} ακόμη εξαγωγή σε αναμονή|{n} ακόμη εξαγωγές σε αναμονή
Continue in Background Συνέχεια στο παρασκήνιο
Cancel Export Ακύρωση εξαγωγής
@msg Export cancelled Η εξαγωγή ακυρώθηκε
# Paragraph panel menu › Justification…
Desired Επιθυμητό
Word Spacing: Απόσταση λέξεων:
Can't render this file because it contains an unexpected character in line 2006 and column 19.
+6
View File
@@ -5130,6 +5130,12 @@ mark-style Japanese Japonesas
@msg too many library panels were opened in this session se abrieron demasiados paneles de biblioteca en esta sesión
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. La imagen tiene {_1} × {_2} píxeles, más de {LARGE_TRACE_SIDE} en un lado: se calcará, pero más despacio. Un ajuste preestablecido de menor fidelidad o una copia más pequeña de la imagen se calcan más rápido.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). La imagen se calcó a {_1} × {_2} píxeles, reducida desde {_3} × {_4} ({_5} megapíxeles como máximo).
Artboard {n} of {total} Mesa de trabajo {n} de {total}
Encoding and writing the file… Codificando y escribiendo el archivo…
@plural {n} more export waiting|{n} more exports waiting {n} exportación más en espera|{n} exportaciones más en espera
Continue in Background Continuar en segundo plano
Cancel Export Cancelar exportación
@msg Export cancelled Exportación cancelada
# Paragraph panel menu › Justification…
Desired Deseado
Word Spacing: Espaciado entre palabras:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5152,6 +5152,12 @@ mark-style Japanese Japanilainen
@msg too many library panels were opened in this session tässä istunnossa on avattu liian monta kirjastopaneelia
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Kuva on {_1} × {_2} kuvapistettä, yhdeltä sivultaan yli {LARGE_TRACE_SIDE}: se jäljitetään, mutta hitaammin. Pienemmän tarkkuuden esiasetus tai kuvan pienempi kopio jäljittyy nopeammin.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Kuva jäljitettiin kokoon {_1} × {_2} kuvapistettä pienennettynä koosta {_3} × {_4} (enintään {_5} megapikseliä).
Artboard {n} of {total} Piirtoalusta {n}/{total}
Encoding and writing the file… Tiedostoa koodataan ja kirjoitetaan…
@plural {n} more export waiting|{n} more exports waiting {n} vienti odottaa|{n} vientiä odottaa
Continue in Background Jatka taustalla
Cancel Export Peruuta vienti
@msg Export cancelled Vienti peruutettiin
# Paragraph panel menu › Justification…
Desired Haluttu
Word Spacing: Sanaväli:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5187,6 +5187,12 @@ theme Appearance Apparence
@msg too many library panels were opened in this session trop de panneaux de bibliothèque ont été ouverts dans cette session
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. L’image fait {_1} × {_2} pixels, plus de {LARGE_TRACE_SIDE} sur un côté : elle est vectorisée, mais plus lentement. Un paramètre prédéfini moins fidèle ou une copie plus petite de l’image se vectorise plus vite.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). L’image a été vectorisée à {_1} × {_2} pixels, réduite de {_3} × {_4} ({_5} mégapixels au plus).
Artboard {n} of {total} Plan de travail {n} sur {total}
Encoding and writing the file… Encodage et écriture du fichier…
@plural {n} more export waiting|{n} more exports waiting {n} autre exportation en attente|{n} autres exportations en attente
Continue in Background Continuer en arrière-plan
Cancel Export Annuler l’exportation
@msg Export cancelled Exportation annulée
# Paragraph panel menu › Justification…
Desired Optimal
Word Spacing: Espacement des mots :
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5174,6 +5174,12 @@ imageTrace Outline Körvonal
@msg too many library panels were opened in this session ebben a munkamenetben túl sok könyvtárpanel nyílt meg
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. A kép {_1} × {_2} képpont, egyik oldalán több mint {LARGE_TRACE_SIDE}: a vektorizálás megtörténik, de lassabban. Kisebb hűségű előbeállítással vagy a kép kisebb másolatával gyorsabb.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). A kép vektorizálása {_1} × {_2} képponton történt, {_3} × {_4} méretről kicsinyítve (legfeljebb {_5} megapixel).
Artboard {n} of {total} Rajztábla: {n} / {total}
Encoding and writing the file… A fájl kódolása és írása…
@plural {n} more export waiting|{n} more exports waiting Még {n} exportálás várakozik
Continue in Background Folytatás a háttérben
Cancel Export Exportálás megszakítása
@msg Export cancelled Az exportálás megszakítva
# Paragraph panel menu › Justification…
Desired Kívánt
Word Spacing: Szóköz:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5172,6 +5172,12 @@ swatch Bright Violet Violet cerah
Typing Mengetik
Persistent Persisten
Library Panel Options Opsi panel pustaka
Artboard {n} of {total} Artboard {n} dari {total}
Encoding and writing the file… Mengodekan dan menulis berkas…
@plural {n} more export waiting|{n} more exports waiting {n} ekspor lagi menunggu
Continue in Background Lanjutkan di latar belakang
Cancel Export Batalkan ekspor
@msg Export cancelled Ekspor dibatalkan
# Paragraph panel menu › Justification…
Desired Diinginkan
Word Spacing: Spasi Kata:
Can't render this file because it contains an unexpected character in line 3768 and column 19.
+6
View File
@@ -4801,6 +4801,12 @@ lighting Light Luce
@msg too many library panels were opened in this session in questa sessione sono stati aperti troppi pannelli libreria
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. L’immagine è di {_1} × {_2} pixel, più di {LARGE_TRACE_SIDE} su un lato: viene vettorializzata, ma più lentamente. Un predefinito a fedeltà più bassa o una copia più piccola dell’immagine è più veloce.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). L’immagine è stata vettorializzata a {_1} × {_2} pixel, ridotta da {_3} × {_4} (al massimo {_5} megapixel).
Artboard {n} of {total} Tavola da disegno {n} di {total}
Encoding and writing the file… Codifica e scrittura del file…
@plural {n} more export waiting|{n} more exports waiting {n} altra esportazione in attesa|{n} altre esportazioni in attesa
Continue in Background Continua in background
Cancel Export Annulla esportazione
@msg Export cancelled Esportazione annullata
# Paragraph panel menu › Justification…
Desired Desiderato
Word Spacing: Spaziatura parole:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4841,6 +4841,12 @@ lighting Light ライト
@msg too many library panels were opened in this session このセッションで開いたライブラリパネルが多すぎます
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. 画像は {_1} × {_2} ピクセルで、一辺が {LARGE_TRACE_SIDE} を超えています。トレースはできますが、時間がかかります。忠実度の低いプリセットや画像の小さいコピーを使うと速くなります。
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). 画像は {_3} × {_4} から縮小した {_1} × {_2} ピクセルでトレースされました (最大 {_5} メガピクセル)。
Artboard {n} of {total} アートボード {n} / {total}
Encoding and writing the file… ファイルをエンコードして書き込み中…
@plural {n} more export waiting|{n} more exports waiting 待機中の書き出し: あと {n} 件
Continue in Background バックグラウンドで続行
Cancel Export 書き出しをキャンセル
@msg Export cancelled 書き出しをキャンセルしました
# Paragraph panel menu › Justification…
Desired 最適
Word Spacing: 単語間隔:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4764,6 +4764,12 @@ lighting Light 광원
@msg too many library panels were opened in this session 이 세션에서 라이브러리 패널을 너무 많이 열었습니다
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. 이미지가 {_1} × {_2}픽셀로 한 변이 {LARGE_TRACE_SIDE}을 넘습니다. 추적은 되지만 더 느립니다. 충실도가 낮은 사전 설정이나 이미지의 작은 사본을 사용하면 더 빠릅니다.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). 이미지를 {_3} × {_4}에서 축소한 {_1} × {_2}픽셀로 추적했습니다(최대 {_5}메가픽셀).
Artboard {n} of {total} 아트보드 {n} / {total}
Encoding and writing the file… 파일을 인코딩하고 쓰는 중…
@plural {n} more export waiting|{n} more exports waiting 대기 중인 내보내기 {n}개 더
Continue in Background 백그라운드에서 계속
Cancel Export 내보내기 취소
@msg Export cancelled 내보내기를 취소했습니다
# Paragraph panel menu › Justification…
Desired 희망
Word Spacing: 단어 간격:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6 -1
View File
@@ -5215,7 +5215,12 @@ swatch Bright Violet Felviolet
@msg too many library panels were opened in this session er zijn in deze sessie te veel bibliotheekpanelen geopend
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. De afbeelding is {_1} × {_2} pixels, meer dan {LARGE_TRACE_SIDE} aan één zijde: ze wordt getraceerd, maar langzamer. Een voorinstelling met minder detail of een kleinere kopie van de afbeelding traceert sneller.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). De afbeelding is getraceerd op {_1} × {_2} pixels, verkleind van {_3} × {_4} (maximaal {_5} megapixels).
Artboard {n} of {total} Tekengebied {n} van {total}
Encoding and writing the file… Bestand coderen en schrijven…
@plural {n} more export waiting|{n} more exports waiting Nog {n} export in de wachtrij|Nog {n} exports in de wachtrij
Continue in Background Doorgaan op achtergrond
Cancel Export Export annuleren
@msg Export cancelled Export geannuleerd
# Paragraph panel menu › Justification…
Desired Gewenst
Word Spacing: Woordafstand:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5040,6 +5040,12 @@ mark-style Japanese Japońskie
@msg too many library panels were opened in this session w tej sesji otwarto zbyt wiele paneli bibliotek
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Obraz ma {_1} × {_2} pikseli, ponad {LARGE_TRACE_SIDE} na jednym boku: zostanie obrysowany, ale wolniej. Ustawienie wstępne o niższej wierności lub mniejsza kopia obrazu obrysuje się szybciej.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Obraz obrysowano w rozdzielczości {_1} × {_2} pikseli, po zmniejszeniu z {_3} × {_4} (najwyżej {_5} megapikseli).
Artboard {n} of {total} Obszar kompozycji {n} z {total}
Encoding and writing the file… Kodowanie i zapisywanie pliku…
@plural {n} more export waiting|{n} more exports waiting Czeka jeszcze {n} eksport|Czekają jeszcze {n} eksporty|Czeka jeszcze {n} eksportów
Continue in Background Kontynuuj w tle
Cancel Export Anuluj eksport
@msg Export cancelled Eksport anulowany
# Paragraph panel menu › Justification…
Desired Optymalne
Word Spacing: Odstępy między wyrazami:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5105,6 +5105,12 @@ mark-style Japanese Japonesas
@msg too many library panels were opened in this session foram abertos painéis de biblioteca demais nesta sessão
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. A imagem tem {_1} × {_2} pixels, mais de {LARGE_TRACE_SIDE} em um lado: ela é traçada, mas mais devagar. Uma predefinição de menor fidelidade ou uma cópia menor da imagem traça mais rápido.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). A imagem foi traçada em {_1} × {_2} pixels, reduzida de {_3} × {_4} (no máximo {_5} megapixels).
Artboard {n} of {total} Mesa de Trabalho {n} de {total}
Encoding and writing the file… Codificando e gravando o arquivo…
@plural {n} more export waiting|{n} more exports waiting Mais {n} exportação aguardando|Mais {n} exportações aguardando
Continue in Background Continuar em Segundo Plano
Cancel Export Cancelar Exportação
@msg Export cancelled Exportação cancelada
# Paragraph panel menu › Justification…
Desired Desejado
Word Spacing: Espaçamento entre Palavras:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -4797,6 +4797,12 @@ lighting Light Свет
@msg too many library panels were opened in this session в этом сеансе открыто слишком много панелей библиотек
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Изображение {_1} × {_2} пикселей, больше {LARGE_TRACE_SIDE} по одной стороне: трассировка выполняется, но медленнее. Стиль с меньшей точностью или уменьшенная копия изображения трассируются быстрее.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Изображение трассировано в размере {_1} × {_2} пикселей, уменьшенное с {_3} × {_4} (не более {_5} мегапикселей).
Artboard {n} of {total} Монтажная область {n} из {total}
Encoding and writing the file… Кодирование и запись файла…
@plural {n} more export waiting|{n} more exports waiting Ещё {n} экспорт в очереди|Ещё {n} экспорта в очереди|Ещё {n} экспортов в очереди
Continue in Background Продолжить в фоне
Cancel Export Отменить экспорт
@msg Export cancelled Экспорт отменён
# Paragraph panel menu › Justification…
Desired Желательно
Word Spacing: Интервал между словами:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5144,6 +5144,12 @@ theme Appearance Utseende
@msg too many library panels were opened in this session för många bibliotekspaneler har öppnats i den här sessionen
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Bilden är {_1} × {_2} pixlar, mer än {LARGE_TRACE_SIDE} på en sida: den vektoriseras, men långsammare. En förinställning med lägre återgivning eller en mindre kopia av bilden går snabbare.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Bilden vektoriserades vid {_1} × {_2} pixlar, nedskalad från {_3} × {_4} (högst {_5} megapixlar).
Artboard {n} of {total} Rityta {n} av {total}
Encoding and writing the file… Kodar och skriver filen…
@plural {n} more export waiting|{n} more exports waiting {n} export till väntar|{n} exporter till väntar
Continue in Background Fortsätt i bakgrunden
Cancel Export Avbryt export
@msg Export cancelled Exporten avbröts
# Paragraph panel menu › Justification…
Desired Önskat
Word Spacing: Ordavstånd:
Can't render this file because it contains an unexpected character in line 5 and column 25.
+6
View File
@@ -4781,6 +4781,12 @@ lighting Light Світло
@msg too many library panels were opened in this session у цьому сеансі відкрито забагато панелей бібліотек
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Зображення {_1} × {_2} пікселів, понад {LARGE_TRACE_SIDE} з одного боку: трасування виконується, але повільніше. Стиль із нижчою точністю або менша копія зображення трасуються швидше.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Зображення трасовано в розмірі {_1} × {_2} пікселів, зменшене з {_3} × {_4} (щонайбільше {_5} мегапікселів).
Artboard {n} of {total} Монтажна область {n} з {total}
Encoding and writing the file… Кодування та запис файлу…
@plural {n} more export waiting|{n} more exports waiting Ще {n} експорт в очікуванні|Ще {n} експорти в очікуванні|Ще {n} експортів в очікуванні
Continue in Background Продовжити у фоні
Cancel Export Скасувати експорт
@msg Export cancelled Експорт скасовано
# Paragraph panel menu › Justification…
Desired Бажане
Word Spacing: Інтервал між словами:
Can't render this file because it contains an unexpected character in line 725 and column 40.
+6
View File
@@ -4781,6 +4781,12 @@ theme Appearance Giao diện
@msg too many library panels were opened in this session đã mở quá nhiều bảng thư viện trong phiên này
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Ảnh có kích thước {_1} × {_2} điểm ảnh, hơn {LARGE_TRACE_SIDE} ở một cạnh: ảnh vẫn được dò, nhưng chậm hơn. Một thiết lập sẵn có độ trung thực thấp hơn hoặc một bản sao nhỏ hơn của ảnh sẽ dò nhanh hơn.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Ảnh đã được dò ở {_1} × {_2} điểm ảnh, thu nhỏ từ {_3} × {_4} (tối đa {_5} megapixel).
Artboard {n} of {total} Bảng vẽ {n} / {total}
Encoding and writing the file… Đang mã hóa và ghi tệp…
@plural {n} more export waiting|{n} more exports waiting Còn {n} lượt xuất đang chờ
Continue in Background Tiếp tục trong nền
Cancel Export Hủy xuất
@msg Export cancelled Đã hủy xuất
# Paragraph panel menu › Justification…
Desired Mong muốn
Word Spacing: Khoảng cách từ:
Can't render this file because it contains an unexpected character in line 3948 and column 10.
+6 -1
View File
@@ -5023,7 +5023,12 @@ lighting Light 光源
@msg only http, https and mailto URLs open in the browser, not “{url}” 浏览器中只会打开 http、https 和 mailto 的 URL,不会打开 “{url}”
@msg {id} opens another app: only the person at the keyboard can run it {id} 会打开其他应用程序:只有使用键盘的人才能运行它
@msg “{name}” is not a file “{name}” 不是文件
Artboard {n} of {total} 画板 {n} / {total}
Encoding and writing the file… 正在编码并写入文件…
@plural {n} more export waiting|{n} more exports waiting 另有 {n} 个导出等待中
Continue in Background 在后台继续
Cancel Export 取消导出
@msg Export cancelled 已取消导出
# Paragraph panel menu › Justification…
Desired 所需
Word Spacing: 字间距:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -4077,6 +4077,12 @@ lighting Light 光源
Enable Guides for 9-Slice Scaling 啟用 9-slice 縮放參考線
Persistent 持久
Library Panel Options 資料庫面板選項
Artboard {n} of {total} 工作區域 {n} / {total}
Encoding and writing the file… 正在編碼並寫入檔案…
@plural {n} more export waiting|{n} more exports waiting 另有 {n} 個匯出等待中
Continue in Background 在背景繼續
Cancel Export 取消匯出
@msg Export cancelled 已取消匯出
# Paragraph panel menu › Justification…
Desired 所需
Word Spacing: 字間距:
Can't render this file because it contains an unexpected character in line 6 and column 47.
+42 -8
View File
@@ -3,7 +3,9 @@
use serde_json::{Value, json};
use vectorcraft_doc::SavedView;
use vectorcraft_doc::cancel;
use vectorcraft_engine::EngineError;
use vectorcraft_engine::cmd::fileio::progress::{self, Progress};
use vectorcraft_engine::cmd::fileio::{self, Format, SAVE_FORMATS, SaveMode, SavePlan, TEMPLATE_FORMATS};
use crate::background::{self, Writer};
@@ -675,6 +677,25 @@ pub fn recent_files(app: &VectorcraftApp) -> &[String] {
&r[..r.len().min(app.session.prefs.recent_files_count as usize)]
}
/// The status once an export was cancelled.
pub const EXPORT_CANCELLED: &str = "Export cancelled";
/// File → Cancel Export (the progress dialog's Cancel): every export running in the background
/// stops and writes nothing more → `{cancelled: n}`.
pub fn cancel_exports(app: &mut VectorcraftApp) -> Value {
let n = app.background.cancel_exports();
if n > 0 {
app.status(EXPORT_CANCELLED);
}
json!({ "cancelled": n })
}
/// The progress dialog's Continue in Background: the exports go on, shown in the status bar only
/// → `{hidden: n}`.
pub fn hide_exports(app: &mut VectorcraftApp) -> Value {
json!({ "hidden": app.background.hide_exports() })
}
/// Export the active document in `format` (default: the path's extension, else PNG) with the
/// `document.export` options in `params` (artboard, range, useArtboards, selectedOnly, ppi, SVG
/// options…): every file it writes (one per artboard, linked images) goes next to `path`. The
@@ -704,18 +725,31 @@ pub fn export(app: &mut VectorcraftApp, format: Option<&str>, path: Option<Strin
// Background Export: encoded and written from this snapshot while editing goes on.
let label = format!("Exporting {}", fileio::file_name(&path));
let (params, target) = (params.clone(), path.clone());
// The progress dialog's counts and Cancel: a cancelled export stops drawing and never starts
// writing.
let progress = Progress::new();
let (reporting, asked) = (progress.clone(), progress.clone());
let work = move |write: Writer| {
let enc = fileio::encode_all(&doc, f.id, &params).map_err(|e| e.to_string())?;
let files = write_encoded(write, &doc, &path, &enc)?;
let mut out = json!({ "path": files.first().unwrap_or(&path), "warnings": enc.warnings });
if files.len() > 1 {
out["files"] = json!(files);
}
Ok(out)
progress::scope(&reporting, || {
cancel::check().map_err(|e| e.to_string())?;
let enc = fileio::encode_all(&doc, f.id, &params).map_err(|e| e.to_string())?;
cancel::check().map_err(|e| e.to_string())?;
let files = write_encoded(write, &doc, &path, &enc)?;
let mut out = json!({ "path": files.first().unwrap_or(&path), "warnings": enc.warnings });
if files.len() > 1 {
out["files"] = json!(files);
}
Ok(out)
})
};
let enabled = app.session.prefs.background_export;
let failed = target.clone();
let mut r = background::run(app, enabled, label, None, work, move |app, r| {
let mut r = background::run_with_progress(app, enabled, label, None, Some(progress), work, move |app, r| {
// A file written before the Cancel arrived is reported as written.
if r.is_err() && asked.cancelled() {
app.status(EXPORT_CANCELLED);
return Ok(json!({ "cancelled": true }));
}
let r = r.inspect_err(|e| report_write_error(app, true, &failed, e))?;
let what = match r["files"].as_array() {
Some(files) => format!("{} files", files.len()),
+2
View File
@@ -25,6 +25,7 @@ pub mod cursors;
pub mod dialogs;
pub mod dock;
mod edit_toolbar;
pub mod export_progress;
pub mod find_font;
pub mod floating;
pub mod font_menu;
@@ -1440,6 +1441,7 @@ impl VectorcraftApp {
dock::floating_panel(self, &ctx);
floating::show(self, &ctx);
dialogs::show(self, &ctx);
export_progress::show(self, &ctx);
palette::show(self, &ctx);
if self.custom_titlebar {
titlebar::resize_zones(ui);
+15
View File
@@ -747,6 +747,18 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
("file.openRecent29", "Open Recent File 29", "", "{}"),
("file.openRecent30", "Open Recent File 30", "", "{}"),
("file.reveal", "Show in Folder", "", "{} show the document's file in the system file manager (desktop) → {path}"),
(
"file.cancelExport",
"Cancel Export",
"",
"{} stop every export running in the background (Background Export): an export stops between artboards and writes no file once cancelled → {cancelled: n}",
),
(
"file.exportInBackground",
"Continue in Background",
"",
"{} close the export progress dialog; the exports go on, shown in the status bar → {hidden: n}",
),
(
"ui.dxfOptionsDialog",
"DXF Options Dialog",
@@ -1101,6 +1113,8 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
"file.newFromTemplate" if p.get("dataBase64").is_none() => io::new_from_template(app, s("path")),
"file.revert" if p.get("confirmed").and_then(Value::as_bool) != Some(true) => io::ask_revert(app),
"file.reveal" => io::reveal(app),
"file.cancelExport" => Ok(io::cancel_exports(app)),
"file.exportInBackground" => Ok(io::hide_exports(app)),
id if id.starts_with("file.openRecent") => match recent_slot(app, id).cloned() {
Some(path) => io::open_reporting(app, &path),
None => Err("no such recent file".into()),
@@ -2225,6 +2239,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool {
"type.bold" | "type.italic" => crate::panels::character::text_style(app).is_some(),
id if crate::panels::paragraph::ALIGN_COMMANDS.iter().any(|c| c.0 == id) => crate::panels::character::text_style(app).is_some(),
"file.reveal" => app.services.reveal.is_some() && app.session.active().is_some_and(|d| d.path.is_some()),
"file.cancelExport" | "file.exportInBackground" => app.background.jobs.iter().any(|j| j.progress.is_some() && !j.cancelled()),
"file.place"
| "file.export.svg"
| "file.export.png"
+28
View File
@@ -121,6 +121,34 @@ fn exports_run_in_the_background_and_the_preference_turns_both_off() {
assert!(app.background.jobs.is_empty());
}
#[test]
fn a_cancelled_export_writes_nothing() {
let (gate, writing) = (Gate::default(), Gate::default());
let files = Files::default();
let (f, g, w) = (files.clone(), gate.clone(), writing.clone());
let services = Services {
write_shared: Some(Arc::new(move |p: &str, b: &[u8]| {
w.open();
g.wait();
f.lock().unwrap().push((p.to_string(), b.to_vec()));
Ok(())
})),
..Default::default()
};
let mut app = VectorcraftApp::new(Session::new(), services);
app.run("file.new", json!({"width": 100, "height": 100})).unwrap();
// The first export holds the worker at the gate, past its last check: it still writes.
io::export(&mut app, Some("svg"), Some("/out/first.svg".into()), &json!({})).unwrap();
writing.wait();
io::export(&mut app, Some("png"), Some("/out/second.png".into()), &json!({})).unwrap();
assert_eq!(app.run("file.cancelExport", json!({})).unwrap()["cancelled"], 2);
assert_eq!(app.run("file.cancelExport", json!({})).unwrap()["cancelled"], 0, "nothing left to cancel");
gate.open();
background::wait_all(&mut app);
assert_eq!(written(&files), ["/out/first.svg"]);
assert_eq!(app.ui.status, io::EXPORT_CANCELLED);
}
#[test]
fn quitting_waits_for_background_saves() {
let gate = Gate::default();
+3 -1
View File
File diff suppressed because one or more lines are too long
+3 -1
View File
File diff suppressed because one or more lines are too long