mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 05:37:23 -06:00
Export for Screens: the dialog is laid out as Illustrator's
This commit is contained in:
@@ -1,34 +1,31 @@
|
||||
//! File → Export → Export for Screens: the Artboards tab (All, a range or Full Document, Include
|
||||
//! Bleed, thumbnails with checkboxes) and the Assets tab (the Asset Export panel's assets, with
|
||||
//! checkboxes), the destination (folder picker, Open Location after Export, Create Sub-folders;
|
||||
//! the web downloads instead), the format rows (scale as `2x`, `100w`, `100h` or `72ppi`, suffix,
|
||||
//! format), the presets, Format Settings (the gear) and the prefix. It opens on the settings the
|
||||
//! document last exported with, except that while the Artboard tool is in use the artboards it has
|
||||
//! selected are the ones chosen; the format rows are the Asset Export panel's too ([`formats`]).
|
||||
//! File → Export → Export for Screens, laid out as Illustrator's: on the left the Artboards and
|
||||
//! Assets tabs (thumbnails with checkboxes, in a grid or a list), Clear Selection and the prefix;
|
||||
//! on the right Select (All, a range or Full Document, Include Bleed), the destination (folder
|
||||
//! picker, Open Location after Export, Create Sub-folders by scale or by format; the web downloads
|
||||
//! instead), Export PDFs As (one file of every artboard or a file each), and the format rows (scale
|
||||
//! as `2x`, `100w`, `100h` or `72ppi`, suffix, format) with the iOS and Android rows, the format
|
||||
//! presets (the ☰ menu) and Format Settings (the gear); below, how many items are selected and
|
||||
//! how many files the export writes. It opens on the settings the document last exported with,
|
||||
//! except that while the Artboard tool is in use the artboards it has selected are the ones chosen;
|
||||
//! the format rows are the Asset Export panel's too ([`formats`]). On the desktop with Background
|
||||
//! Export on, the files are encoded and written on the background thread
|
||||
//! ([`crate::background`]) after the dialog closes.
|
||||
//!
|
||||
//! The Layers tab exports each checked layer's art alone, and the Selection tab the selected art as
|
||||
//! one file or a file per object. A file name pattern (`name`: `<filename>`, `<name>`, `####`,
|
||||
//! `YYYYMMDD`…, see `document.exportForScreens`) names every file alike, with a live example, and
|
||||
//! the files go into one folder, or a folder per row, format, item, or item and format
|
||||
//! (`folders`). Export presets (built-in and saved) set the formats, naming and folders at once.
|
||||
//! On the desktop with Background Export on, the files are encoded and written on the background
|
||||
//! thread ([`crate::background`]) after the dialog closes.
|
||||
//! `document.exportForScreens` does more than the dialog shows (layers, the selection, file name
|
||||
//! patterns, a folder per item): those are for scripts and MCP.
|
||||
//!
|
||||
//! Fields (`ui.dialog.set`): `tab` (artboards|assets|layers|selection), `select` (all|range|full), `range`,
|
||||
//! Fields (`ui.dialog.set`): `tab` (artboards|assets), `select` (all|range|full), `range`,
|
||||
//! `boards` ([bool] per artboard), `assets` ([asset id] checked on the Assets tab), `includeBleed`,
|
||||
//! `folder`, `openLocation`, `subfolders`, `prefix`, `preset` (""|mobile|density: where the rows
|
||||
//! came from), `formats` (rows of `document.exportForScreens`, `scale` as text), `settings`
|
||||
//! ({png|png8|jpg|webp|gif|tiff|svg|pdf: {…}}), `layers` ([layer id] checked on the Layers tab),
|
||||
//! `selectionMode` (one|each), `name` (the pattern), `counterStart`, `folders`
|
||||
//! (none|rows|format|item|itemFormat), `exportPreset` (the preset applied last, "" for none);
|
||||
//! `folder`, `openLocation`, `subfolders`, `subfolderBy` (scale|format), `pdfs` (single|multiple),
|
||||
//! `prefix`, `preset` (""|mobile|density: where the rows came from), `formats` (rows of
|
||||
//! `document.exportForScreens`, `scale` as text), `settings` ({png|png8|jpg|webp|gif|tiff|svg|pdf:
|
||||
//! {…}}), `view` (grid|list), `exportPreset` (the format preset applied last, "" for none);
|
||||
//! `__settings` names the format whose Format Settings show ("" when closed), `__savePreset` is the
|
||||
//! name being typed to save a preset (absent when not saving).
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Map, Value, json};
|
||||
use vectorcraft_engine::cmd::fileio::{self, ArtboardPick, SCREEN_PRESETS, ScreenSize, pdf};
|
||||
|
||||
use vectorcraft_doc::{ExportAsset, NodeId};
|
||||
use vectorcraft_engine::cmd::fileio::{self, ArtboardPick, ScreenSize, pdf};
|
||||
|
||||
use super::{DialogSpec, form, png_options, svg_options};
|
||||
use crate::state::Dialog;
|
||||
@@ -43,11 +40,9 @@ pub(super) const SPEC: DialogSpec = DialogSpec {
|
||||
ok_label: Some(|app| match (io::is_web(app), app.ui.dialog.as_ref().map(|d| d.str("tab")).as_deref()) {
|
||||
(true, _) => "Download",
|
||||
(false, Some("assets")) => "Export Asset",
|
||||
(false, Some("layers")) => "Export Layer",
|
||||
(false, Some("selection")) => "Export Selection",
|
||||
(false, _) => "Export Artboard",
|
||||
}),
|
||||
min_width: 660.0,
|
||||
min_width: 720.0,
|
||||
..DialogSpec::FORM
|
||||
};
|
||||
|
||||
@@ -80,32 +75,13 @@ fn print_marks() -> Value {
|
||||
json!({"trim": true, "registration": true, "colorBars": true, "pageInfo": true})
|
||||
}
|
||||
|
||||
/// Where the files go: (`folders` value, label).
|
||||
const FOLDERS: [(&str, &str); 5] = [
|
||||
("none", "All in One Folder"),
|
||||
("rows", "By Size or Row"),
|
||||
("format", "By Format"),
|
||||
("item", "By Artboard or Item"),
|
||||
("itemFormat", "By Item, Then Format"),
|
||||
];
|
||||
const FOLDER_LABELS: [&str; 5] = ["All in One Folder", "By Size or Row", "By Format", "By Artboard or Item", "By Item, Then Format"];
|
||||
|
||||
/// The tokens Insert adds to the file name pattern: (label, token).
|
||||
const TOKENS: [(&str, &str); 8] = [
|
||||
("Document Name", "<filename>"),
|
||||
("Artboard or Item Name", "<name>"),
|
||||
("Number (####)", "####"),
|
||||
("Date (YYYYMMDD)", "YYYYMMDD"),
|
||||
("Date (YYYY-MM-DD)", "YYYY-MM-DD"),
|
||||
("Time (hhmmss)", "<time>"),
|
||||
("Format", "<format>"),
|
||||
("Scale", "<scale>"),
|
||||
];
|
||||
const TOKEN_LABELS: [&str; 8] =
|
||||
["Document Name", "Artboard or Item Name", "Number (####)", "Date (YYYYMMDD)", "Date (YYYY-MM-DD)", "Time (hhmmss)", "Format", "Scale"];
|
||||
|
||||
/// The tabs: (`tab` value, label).
|
||||
const TABS: [&str; 4] = ["artboards", "assets", "layers", "selection"];
|
||||
const TABS: [&str; 2] = ["artboards", "assets"];
|
||||
|
||||
/// The left pane's width and the height of its thumbnails' area; the right pane's width.
|
||||
const LEFT_W: f32 = 300.0;
|
||||
const PANE_H: f32 = 360.0;
|
||||
const RIGHT_W: f32 = 370.0;
|
||||
|
||||
/// The formats Format Settings has options for: (format, label).
|
||||
const SETTINGS: [(&str, &str); 8] =
|
||||
@@ -144,8 +120,9 @@ pub(crate) fn open_assets(app: &mut VectorcraftApp, checked: Option<&[u64]>) {
|
||||
}
|
||||
|
||||
/// Open the dialog on the settings the active document last exported with (else one PNG row at
|
||||
/// 1x, into the Desktop), every asset checked. While the Artboard tool is in use, the artboards it
|
||||
/// has selected (the rows the Artboards panel highlights) are the ones chosen instead.
|
||||
/// 1x, into the Desktop, with Create Sub-folders on), every asset checked. While the Artboard tool
|
||||
/// is in use, the artboards it has selected (the rows the Artboards panel highlights) are the ones
|
||||
/// chosen instead.
|
||||
pub fn open(app: &mut VectorcraftApp) {
|
||||
let (n, saved, assets) = app
|
||||
.session
|
||||
@@ -172,12 +149,14 @@ pub fn open(app: &mut VectorcraftApp) {
|
||||
settings.insert(id.into(), Value::Object(saved_options(app, &saved, id)));
|
||||
}
|
||||
let folder = get("folder").and_then(Value::as_str).filter(|f| !f.is_empty()).map(str::to_string).or_else(fileio::export_folder);
|
||||
// Where files go: the saved choice, else a folder per row as Create Sub-folders did.
|
||||
let folders = get("folders")
|
||||
.and_then(Value::as_str)
|
||||
.filter(|f| FOLDERS.iter().any(|(id, _)| id == f))
|
||||
.map_or_else(|| if flag("subfolders", preset.is_some()) { "rows" } else { "none" }.to_string(), str::to_string);
|
||||
let layers: Vec<u64> = app.session.active().map(|st| st.doc.layers.iter().map(|l| l.id.0).collect()).unwrap_or_default();
|
||||
// Create Sub-folders is on until an export turns it off; a folder per format is the saved
|
||||
// `folders: "format"`, any other sorting a folder per scale.
|
||||
let folders = get("folders").and_then(Value::as_str);
|
||||
let subfolders = match folders {
|
||||
Some("none") => false,
|
||||
Some(_) => true,
|
||||
None => flag("subfolders", saved.is_empty() || preset.is_some()),
|
||||
};
|
||||
let fields = json!({
|
||||
"tab": "artboards",
|
||||
"select": select,
|
||||
@@ -187,18 +166,16 @@ pub fn open(app: &mut VectorcraftApp) {
|
||||
"includeBleed": flag("includeBleed", false),
|
||||
"folder": folder.unwrap_or_default(),
|
||||
"openLocation": flag("openLocation", true),
|
||||
"subfolders": flag("subfolders", preset.is_some()),
|
||||
"subfolders": subfolders,
|
||||
"subfolderBy": if folders == Some("format") { "format" } else { "scale" },
|
||||
"pdfs": if get("pdfs").and_then(Value::as_str) == Some("single") { "single" } else { "multiple" },
|
||||
"prefix": get("prefix").and_then(Value::as_str).unwrap_or_default(),
|
||||
"preset": preset.unwrap_or_default(),
|
||||
"formats": rows,
|
||||
"settings": settings,
|
||||
"view": "grid",
|
||||
"__settings": "",
|
||||
"__presets": pdf::presets(&app.session),
|
||||
"layers": layers,
|
||||
"selectionMode": get("selection").and_then(Value::as_str).filter(|m| *m == "each").unwrap_or("one"),
|
||||
"name": get("name").and_then(Value::as_str).unwrap_or_default(),
|
||||
"counterStart": get("counterStart").and_then(Value::as_u64).unwrap_or(1),
|
||||
"folders": folders,
|
||||
"exportPreset": "",
|
||||
"__exportPresets": export_presets(app),
|
||||
});
|
||||
@@ -281,24 +258,6 @@ pub(super) fn export_presets(app: &mut VectorcraftApp) -> Value {
|
||||
app.run("document.exportPresets.list", json!({})).ok().and_then(|r| r.get("presets").cloned()).unwrap_or_else(|| json!([]))
|
||||
}
|
||||
|
||||
/// The art of layer `id` (sublayers unfolded), back to front: what its file holds.
|
||||
fn layer_art(doc: &vectorcraft_doc::Document, id: NodeId, out: &mut Vec<NodeId>) {
|
||||
let Some(n) = doc.node(id) else { return };
|
||||
if n.is_layer() {
|
||||
for c in n.children().into_iter().flatten() {
|
||||
layer_art(doc, c.id, out);
|
||||
}
|
||||
} else {
|
||||
out.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// The checked layers that are still in the document, in stacking order.
|
||||
fn checked_layers(app: &VectorcraftApp, d: &Dialog) -> Vec<u64> {
|
||||
let on: Vec<u64> = d.fields.get("layers").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_u64).collect()).unwrap_or_default();
|
||||
app.session.active().map(|st| st.doc.layers.iter().map(|l| l.id.0).filter(|id| on.contains(id)).collect()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The `document.exportForScreens` params the dialog describes (the web: no folder, one ZIP for
|
||||
/// several files).
|
||||
fn params(app: &VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
@@ -316,24 +275,24 @@ fn params(app: &VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
Ok(Value::Object(p))
|
||||
}
|
||||
|
||||
/// The counter's first value: the Start at field (a number, or text typed through `ui.dialog.set`).
|
||||
fn counter_start(d: &Dialog) -> u64 {
|
||||
d.fields.get("counterStart").and_then(|v| v.as_u64().or_else(|| v.as_str().and_then(|t| t.trim().parse().ok()))).unwrap_or(1)
|
||||
}
|
||||
|
||||
/// The params of [`params`] without the destination (what is exported, at which sizes, and how the
|
||||
/// files are named and sorted), and how many files that is.
|
||||
fn settings_params(app: &VectorcraftApp, d: &Dialog) -> Result<(Map<String, Value>, usize), String> {
|
||||
let (p, files, _) = selection_params(app, d)?;
|
||||
Ok((p, files))
|
||||
}
|
||||
|
||||
/// [`settings_params`] and how many artboards or assets are chosen.
|
||||
fn selection_params(app: &VectorcraftApp, d: &Dialog) -> Result<(Map<String, Value>, usize, usize), String> {
|
||||
let n = app.session.active().map_or(0, |st| st.doc.artboards.len());
|
||||
let rows = d.fields.get("formats").and_then(Value::as_array).cloned().unwrap_or_default();
|
||||
if rows.is_empty() {
|
||||
return Err("add a format to export".into());
|
||||
}
|
||||
let mut p = Map::new();
|
||||
// What each row writes a file of: the checked assets, layers, the selection, else the
|
||||
// artboards.
|
||||
let tab = d.str("tab");
|
||||
let pieces = if tab == "assets" {
|
||||
// What each row writes a file of: the checked assets, else the artboards.
|
||||
let artboards = d.str("tab") != "assets";
|
||||
let pieces = if !artboards {
|
||||
let checked = checked_assets(app, d);
|
||||
if checked.is_empty() {
|
||||
return Err("check an asset to export".into());
|
||||
@@ -341,22 +300,6 @@ fn settings_params(app: &VectorcraftApp, d: &Dialog) -> Result<(Map<String, Valu
|
||||
let count = checked.len();
|
||||
p.insert("assets".into(), json!(checked));
|
||||
count
|
||||
} else if tab == "layers" {
|
||||
let checked = checked_layers(app, d);
|
||||
if checked.is_empty() {
|
||||
return Err("check a layer to export".into());
|
||||
}
|
||||
let count = checked.len();
|
||||
p.insert("layers".into(), json!(checked));
|
||||
count
|
||||
} else if tab == "selection" {
|
||||
let selected = app.session.active().map_or(0, |st| st.selection.objects.len());
|
||||
if selected == 0 {
|
||||
return Err("select the art to export first".into());
|
||||
}
|
||||
let each = d.str("selectionMode") == "each";
|
||||
p.insert("selection".into(), json!(if each { "each" } else { "one" }));
|
||||
if each { selected } else { 1 }
|
||||
} else {
|
||||
match d.str("select").as_str() {
|
||||
"full" => {
|
||||
@@ -367,34 +310,33 @@ fn settings_params(app: &VectorcraftApp, d: &Dialog) -> Result<(Map<String, Valu
|
||||
let range = d.str("range");
|
||||
// A bad range keeps the dialog open.
|
||||
let chosen = ArtboardPick { range: Some(range.clone()), ..Default::default() }.resolve(n)?.unwrap_or_default();
|
||||
if chosen.is_empty() {
|
||||
return Err("check an artboard to export".into());
|
||||
}
|
||||
p.insert("range".into(), json!(range));
|
||||
chosen.len()
|
||||
}
|
||||
_ => n,
|
||||
}
|
||||
};
|
||||
let files = pieces * rows.len();
|
||||
// Export PDFs As › Single file: every chosen artboard in one PDF per PDF row.
|
||||
let single = artboards && pieces > 1 && d.str("select") != "full" && d.str("pdfs") == "single";
|
||||
let pdf_rows = rows.iter().filter(|r| r["format"].as_str().and_then(fileio::format).is_some_and(|f| f.id == "pdf")).count();
|
||||
let files = if single { (rows.len() - pdf_rows) * pieces + pdf_rows } else { rows.len() * pieces };
|
||||
p.insert("includeBleed".into(), json!(d.bool("includeBleed")));
|
||||
let folders = d.str("folders");
|
||||
let folders = match (FOLDERS.iter().any(|(id, _)| *id == folders), d.bool("subfolders")) {
|
||||
// Create Sub-folders (as the Asset Export panel and older scripts set it) is a folder per
|
||||
// row while Folders is still All in One Folder.
|
||||
(true, true) if folders == "none" => "rows".to_string(),
|
||||
(true, _) => folders,
|
||||
(false, sub) => if sub { "rows" } else { "none" }.to_string(),
|
||||
let folders = match (d.bool("subfolders"), d.str("subfolderBy").as_str()) {
|
||||
(false, _) => "none",
|
||||
(true, "format") => "format",
|
||||
(true, _) => "rows",
|
||||
};
|
||||
// Create Sub-folders, as the Asset Export panel (which shares these settings) still names it.
|
||||
// Create Sub-folders, as the Asset Export panel (which shares these settings) names it.
|
||||
p.insert("subfolders".into(), json!(folders == "rows"));
|
||||
p.insert("folders".into(), json!(folders));
|
||||
p.insert("pdfs".into(), json!(if single { "single" } else { "multiple" }));
|
||||
p.insert("prefix".into(), json!(d.str("prefix")));
|
||||
let name = d.str("name");
|
||||
if !name.trim().is_empty() {
|
||||
p.insert("name".into(), json!(name.trim()));
|
||||
}
|
||||
p.insert("counterStart".into(), json!(counter_start(d)));
|
||||
p.insert("formats".into(), Value::Array(rows));
|
||||
p.insert("settings".into(), d.fields.get("settings").cloned().unwrap_or(json!({})));
|
||||
Ok((p, files))
|
||||
Ok((p, files, pieces))
|
||||
}
|
||||
|
||||
/// Export; the dialog stays open when the export fails.
|
||||
@@ -410,130 +352,218 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let tab = TABS.iter().position(|k| *k == d.str("tab")).unwrap_or(0);
|
||||
if let Some(i) = widgets::tab_bar(ui, &[tl!("Artboards"), tl!("Assets"), tl!("Layers"), tl!("Selection")], tab) {
|
||||
d.fields.insert("tab".into(), json!(TABS.get(i).copied().unwrap_or("artboards")));
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(270.0);
|
||||
match tab {
|
||||
1 => assets(app, ui, d),
|
||||
2 => layers(app, ui, d),
|
||||
3 => selection(app, ui, d),
|
||||
_ => artboards(app, ui, d),
|
||||
ui.set_width(LEFT_W);
|
||||
if let Some(i) = widgets::tab_bar(ui, &[tl!("Artboards"), tl!("Assets")], tab) {
|
||||
d.fields.insert("tab".into(), json!(TABS.get(i).copied().unwrap_or("artboards")));
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
let list = d.str("view") == "list";
|
||||
let (pane, _) = ui.allocate_exact_size(egui::vec2(LEFT_W, PANE_H), egui::Sense::hover());
|
||||
ui.painter().rect_filled(pane, 2.0, t.panel_darker);
|
||||
ui.painter().rect_stroke(pane, 2.0, egui::Stroke::new(1.0, t.divider), egui::StrokeKind::Inside);
|
||||
let mut inner = ui.new_child(egui::UiBuilder::new().max_rect(pane.shrink(8.0)).layout(egui::Layout::top_down(egui::Align::Min)));
|
||||
match tab {
|
||||
1 => assets(app, &mut inner, d, list),
|
||||
_ => artboard_tiles(app, &mut inner, d, list),
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
ui.horizontal(|ui| {
|
||||
for (view, icon, tip) in [("grid", "layout-grid", tl!("Grid View")), ("list", "dc-list-view", tl!("List View"))] {
|
||||
if widgets::icon_button(ui, icon, tip, d.str("view") == view || (view == "grid" && !list && d.str("view") != "list"), 22.0)
|
||||
.clicked()
|
||||
{
|
||||
d.fields.insert("view".into(), json!(view));
|
||||
}
|
||||
}
|
||||
ui.add_space(6.0);
|
||||
if ui.small_button(tl!("Clear Selection")).clicked() {
|
||||
clear_selection(app, d, tab);
|
||||
}
|
||||
widgets::dim_label(ui, tl!("Prefix:"));
|
||||
form::text(ui, d, "prefix", (ui.available_width() - 4.0).max(60.0));
|
||||
});
|
||||
});
|
||||
let (sep, _) = ui.allocate_exact_size(egui::vec2(17.0, 380.0), egui::Sense::hover());
|
||||
let (sep, _) = ui.allocate_exact_size(egui::vec2(17.0, PANE_H + 60.0), egui::Sense::hover());
|
||||
ui.painter().line_segment([sep.center_top(), sep.center_bottom()], egui::Stroke::new(1.0, t.divider));
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(350.0);
|
||||
ui.set_width(RIGHT_W);
|
||||
let shown = d.str("__settings");
|
||||
if SETTINGS.iter().any(|(id, _)| *id == shown) {
|
||||
format_settings(ui, d, &shown);
|
||||
} else {
|
||||
presets(app, ui, d);
|
||||
ui.add_space(8.0);
|
||||
if tab == 0 {
|
||||
select_options(app, ui, d);
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
destination(app, ui, d);
|
||||
ui.add_space(6.0);
|
||||
pdf_mode(ui, d, tab == 0);
|
||||
ui.add_space(10.0);
|
||||
formats(ui, d);
|
||||
ui.add_space(8.0);
|
||||
naming(app, ui, d);
|
||||
formats(ui, d, Some(&mut |ui: &mut egui::Ui, d: &mut Dialog| preset_menu(app, ui, d)));
|
||||
save_preset_row(app, ui, d);
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
let (picked, files) = counts(app, d);
|
||||
let status =
|
||||
crate::i18n::fmt(tl!("Selected: {n}, Total number of files to export: {m}"), &[("n", &picked.to_string()), ("m", &files.to_string())]);
|
||||
widgets::dim_label(ui, &status);
|
||||
false
|
||||
}
|
||||
|
||||
/// How many artboards or assets are chosen and how many files the export writes (0 while the
|
||||
/// settings choose nothing to export).
|
||||
fn counts(app: &VectorcraftApp, d: &Dialog) -> (usize, usize) {
|
||||
match selection_params(app, d) {
|
||||
Ok((_, files, pieces)) => (pieces, files),
|
||||
Err(_) => (if d.str("tab") == "assets" { checked_assets(app, d).len() } else { 0 }, 0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear Selection: no artboard (or, on the Assets tab, no asset) is checked.
|
||||
fn clear_selection(app: &VectorcraftApp, d: &mut Dialog, tab: usize) {
|
||||
if tab == 1 {
|
||||
d.fields.insert("assets".into(), json!([]));
|
||||
} else {
|
||||
let n = app.session.active().map_or(0, |st| st.doc.artboards.len());
|
||||
d.fields.insert("boards".into(), json!(vec![false; n]));
|
||||
set_select(d, "range");
|
||||
d.fields.insert("range".into(), json!(""));
|
||||
}
|
||||
}
|
||||
|
||||
fn set_select(d: &mut Dialog, select: &str) {
|
||||
d.fields.insert("select".into(), json!(select));
|
||||
}
|
||||
|
||||
/// Select (All, Range, Full Document), Include Bleed, and the artboards' thumbnails and checkboxes.
|
||||
fn artboards(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
/// The checked artboards, one per artboard (all checked when the field is short).
|
||||
fn boards(app: &VectorcraftApp, d: &Dialog) -> Vec<bool> {
|
||||
let n = app.session.active().map_or(0, |st| st.doc.artboards.len());
|
||||
let mut boards: Vec<bool> =
|
||||
d.fields.get("boards").and_then(Value::as_array).map(|a| a.iter().map(|b| b.as_bool().unwrap_or(false)).collect()).unwrap_or_default();
|
||||
boards.resize(n, true);
|
||||
let before = boards.clone();
|
||||
boards
|
||||
}
|
||||
|
||||
/// Select: All or a Range (which checks the artboards it names), Include Bleed and Full Document.
|
||||
fn select_options(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
let n = app.session.active().map_or(0, |st| st.doc.artboards.len());
|
||||
let select = d.str("select");
|
||||
widgets::dim_label(ui, tl!("Select:"));
|
||||
ui.horizontal(|ui| {
|
||||
if widgets::radio(ui, tl!("All"), select == "all", true) {
|
||||
set_select(d, "all");
|
||||
boards.iter_mut().for_each(|b| *b = true);
|
||||
d.fields.insert("boards".into(), json!(vec![true; n]));
|
||||
d.fields.insert("range".into(), json!(range_text(&vec![true; n])));
|
||||
}
|
||||
if widgets::radio(ui, tl!("Range:"), select == "range", true) {
|
||||
set_select(d, "range");
|
||||
}
|
||||
ui.add_enabled_ui(select == "range", |ui| {
|
||||
if form::text_named(ui, d, "range", tl!("Range:"), 90.0)
|
||||
if form::text_named(ui, d, "range", tl!("Range:"), 150.0)
|
||||
&& let Ok(Some(chosen)) = (ArtboardPick { range: Some(d.str("range")), ..Default::default() }).resolve(n)
|
||||
{
|
||||
boards = (0..n).map(|i| chosen.contains(&i)).collect();
|
||||
d.fields.insert("boards".into(), json!((0..n).map(|i| chosen.contains(&i)).collect::<Vec<_>>()));
|
||||
}
|
||||
});
|
||||
});
|
||||
if widgets::radio(ui, tl!("Full Document"), select == "full", true) {
|
||||
form::check(ui, d, "includeBleed", tl!("Include Bleed"));
|
||||
if widgets::radio(ui, tl!("Full Document"), d.str("select") == "full", true) {
|
||||
set_select(d, "full");
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
form::check(ui, d, "includeBleed", tl!("Include Bleed"));
|
||||
ui.add_space(6.0);
|
||||
let typed = boards != before;
|
||||
let checked = boards.clone();
|
||||
}
|
||||
|
||||
/// The artboards' thumbnails, each with its checkbox, number and name, in a grid or a list;
|
||||
/// checking them picks them as the range (all of them: All). Dimmed with Full Document.
|
||||
fn artboard_tiles(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, list: bool) {
|
||||
let mut boards = boards(app, d);
|
||||
let before = boards.clone();
|
||||
let names: Vec<String> = app.session.active().map(|s| s.doc.artboards.iter().map(|a| a.name.clone()).collect()).unwrap_or_default();
|
||||
ui.add_enabled_ui(d.str("select") != "full", |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.small_button(tl!("Select All")).clicked() {
|
||||
boards.iter_mut().for_each(|b| *b = true);
|
||||
tiles(ui, "efs-artboards", names.len(), list, |ui, i, r| {
|
||||
match artboard_thumb(app, ui.ctx(), i) {
|
||||
Some(tex) => {
|
||||
let sz = tex.size_vec2();
|
||||
let s = (r.width() / sz.x).min(r.height() / sz.y).min(1.0);
|
||||
let uv = egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0));
|
||||
ui.painter().image(tex.id(), egui::Rect::from_center_size(r.center(), sz * s), uv, egui::Color32::WHITE);
|
||||
}
|
||||
None => {
|
||||
ui.painter().rect_filled(r, 2.0, egui::Color32::WHITE);
|
||||
}
|
||||
}
|
||||
if ui.small_button(tl!("Clear")).clicked() {
|
||||
boards.iter_mut().for_each(|b| *b = false);
|
||||
}
|
||||
});
|
||||
let names: Vec<String> = app.session.active().map(|s| s.doc.artboards.iter().map(|a| a.name.clone()).collect()).unwrap_or_default();
|
||||
egui::ScrollArea::vertical().max_height(250.0).show(ui, |ui| {
|
||||
for (i, name) in names.iter().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
let (r, _) = ui.allocate_exact_size(egui::vec2(46.0, 46.0), egui::Sense::hover());
|
||||
match artboard_thumb(app, ui.ctx(), i) {
|
||||
Some(tex) => {
|
||||
let sz = tex.size_vec2();
|
||||
let s = (46.0 / sz.x.max(sz.y)).min(1.0);
|
||||
let uv = egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0));
|
||||
ui.painter().image(tex.id(), egui::Rect::from_center_size(r.center(), sz * s), uv, egui::Color32::WHITE);
|
||||
}
|
||||
None => {
|
||||
ui.painter().rect_filled(r, 2.0, egui::Color32::WHITE);
|
||||
}
|
||||
}
|
||||
if let Some(b) = boards.get_mut(i)
|
||||
&& crate::widgets::check_name(ui, name, *b)
|
||||
{
|
||||
*b ^= true;
|
||||
}
|
||||
});
|
||||
let name = names.get(i).cloned().unwrap_or_default();
|
||||
(format!("{} {name}", i + 1), name, boards.get(i).copied().unwrap_or(false))
|
||||
})
|
||||
.into_iter()
|
||||
.for_each(|i| {
|
||||
if let Some(b) = boards.get_mut(i) {
|
||||
*b ^= true;
|
||||
}
|
||||
});
|
||||
});
|
||||
// Checking artboards picks them as the range (all of them: All); a typed range keeps its text.
|
||||
if boards != checked {
|
||||
if boards != before {
|
||||
set_select(d, if boards.iter().all(|b| *b) { "all" } else { "range" });
|
||||
d.fields.insert("range".into(), json!(range_text(&boards)));
|
||||
}
|
||||
if typed || boards != checked {
|
||||
d.fields.insert("boards".into(), json!(boards));
|
||||
}
|
||||
}
|
||||
|
||||
/// `count` thumbnails in a grid (or, with `list`, a list) that scrolls: `paint` draws thumbnail `i`
|
||||
/// into its rect and gives its caption, its checkbox's name and whether it is checked → the
|
||||
/// thumbnails whose checkbox (or picture) was clicked.
|
||||
fn tiles(
|
||||
ui: &mut egui::Ui,
|
||||
id: &str,
|
||||
count: usize,
|
||||
list: bool,
|
||||
mut paint: impl FnMut(&mut egui::Ui, usize, egui::Rect) -> (String, String, bool),
|
||||
) -> Vec<usize> {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let mut toggled = vec![];
|
||||
let side = if list { 40.0 } else { 84.0 };
|
||||
let width = ui.available_width();
|
||||
let columns = if list { 1 } else { ((width + 10.0) / (side + 10.0)).floor().max(1.0) as usize };
|
||||
egui::ScrollArea::vertical().id_salt(id).auto_shrink([false, false]).show(ui, |ui| {
|
||||
for start in (0..count).step_by(columns) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 10.0;
|
||||
for i in start..(start + columns).min(count) {
|
||||
let cell = if list { egui::vec2(width - 16.0, side) } else { egui::vec2(side, side + 18.0) };
|
||||
let (r, resp) = ui.allocate_exact_size(cell, egui::Sense::click());
|
||||
let pic = egui::Rect::from_min_size(r.min, egui::vec2(side, side));
|
||||
let (caption, name, checked) = paint(ui, i, pic.shrink(3.0));
|
||||
let stroke = if checked { egui::Stroke::new(2.0, t.accent) } else { egui::Stroke::new(1.0, t.divider) };
|
||||
ui.painter().rect_stroke(pic, 2.0, stroke, egui::StrokeKind::Inside);
|
||||
let label_at = if list { egui::pos2(pic.right() + 8.0, pic.center().y) } else { egui::pos2(pic.left(), pic.bottom() + 9.0) };
|
||||
let galley =
|
||||
ui.painter().layout(caption, egui::FontId::proportional(11.0), t.text, if list { cell.x - side - 12.0 } else { side });
|
||||
ui.painter().galley(egui::pos2(label_at.x, label_at.y - galley.size().y / 2.0), galley, t.text);
|
||||
// The checkbox sits on the thumbnail's lower left corner, as Illustrator's.
|
||||
let bx = egui::Rect::from_min_size(egui::pos2(pic.left() + 4.0, pic.bottom() - 18.0), egui::vec2(14.0, 14.0));
|
||||
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(bx));
|
||||
if widgets::check_box(&mut child, &name, checked, true) || resp.clicked() {
|
||||
toggled.push(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.add_space(6.0);
|
||||
}
|
||||
});
|
||||
toggled
|
||||
}
|
||||
|
||||
/// The checked assets that are still in the document, in panel order.
|
||||
fn checked_assets(app: &VectorcraftApp, d: &Dialog) -> Vec<u64> {
|
||||
let on: Vec<u64> = d.fields.get("assets").and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_u64).collect()).unwrap_or_default();
|
||||
app.session.active().map(|st| st.doc.assets.iter().map(|a| a.id).filter(|id| on.contains(id)).collect()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The Assets tab: each asset's thumbnail and checkbox, Select All and Clear.
|
||||
fn assets(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
/// The Assets tab: each asset's thumbnail with its checkbox and name, in a grid or a list.
|
||||
fn assets(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, list: bool) {
|
||||
let Some(st) = app.session.active() else { return };
|
||||
if st.doc.assets.is_empty() {
|
||||
ui.add_space(24.0);
|
||||
@@ -542,39 +572,30 @@ fn assets(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
}
|
||||
let mut on = checked_assets(app, d);
|
||||
let before = on.clone();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.small_button(tl!("Select All")).clicked() {
|
||||
on = st.doc.assets.iter().map(|a| a.id).collect();
|
||||
}
|
||||
if ui.small_button(tl!("Clear")).clicked() {
|
||||
on.clear();
|
||||
}
|
||||
});
|
||||
// Asked for until the row is in view: the dialog's first frame only measures it.
|
||||
// Asked for until its row is in view: the dialog's first frame only measures it.
|
||||
let scroll_to = d.fields.get("__scrollTo").and_then(Value::as_u64).filter(|id| st.doc.assets.iter().any(|a| a.id == *id));
|
||||
let mut in_view = scroll_to.is_none();
|
||||
egui::ScrollArea::vertical().max_height(330.0).show(ui, |ui| {
|
||||
for a in &st.doc.assets {
|
||||
let row = ui.horizontal(|ui| {
|
||||
let (r, _) = ui.allocate_exact_size(egui::vec2(46.0, 46.0), egui::Sense::hover());
|
||||
crate::panels::asset_export::paint_thumb(ui, st, a, r);
|
||||
let checked = on.contains(&a.id);
|
||||
if crate::widgets::check_name(ui, &a.name, checked) {
|
||||
on.retain(|id| *id != a.id);
|
||||
if !checked {
|
||||
on.push(a.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
if scroll_to == Some(a.id) {
|
||||
if ui.clip_rect().contains_rect(row.response.rect) {
|
||||
in_view = true;
|
||||
} else {
|
||||
row.response.scroll_to_me(Some(egui::Align::Center));
|
||||
}
|
||||
let clicked = tiles(ui, "efs-assets", st.doc.assets.len(), list, |ui, i, r| {
|
||||
let Some(a) = st.doc.assets.get(i) else { return (String::new(), String::new(), false) };
|
||||
crate::panels::asset_export::paint_thumb(ui, st, a, r);
|
||||
if scroll_to == Some(a.id) {
|
||||
if ui.clip_rect().contains_rect(r) {
|
||||
in_view = true;
|
||||
} else {
|
||||
ui.scroll_to_rect(r, Some(egui::Align::Center));
|
||||
}
|
||||
}
|
||||
(a.name.clone(), a.name.clone(), on.contains(&a.id))
|
||||
});
|
||||
for i in clicked {
|
||||
if let Some(a) = st.doc.assets.get(i) {
|
||||
if on.contains(&a.id) {
|
||||
on.retain(|id| *id != a.id);
|
||||
} else {
|
||||
on.push(a.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if in_view {
|
||||
d.fields.remove("__scrollTo");
|
||||
}
|
||||
@@ -583,8 +604,8 @@ fn assets(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Export to: the folder (typed or picked), Open Location after Export and Create Sub-folders; the
|
||||
/// web downloads instead.
|
||||
/// Export to: the folder (typed or picked), Open Location after Export, and Create Sub-folders,
|
||||
/// a folder per scale or per format; the web downloads instead.
|
||||
fn destination(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
widgets::dim_label(ui, tl!("Export to:"));
|
||||
if io::is_web(app) {
|
||||
@@ -592,78 +613,111 @@ fn destination(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
} else {
|
||||
let pick = app.services.pick_folder.is_some();
|
||||
ui.horizontal(|ui| {
|
||||
form::text(ui, d, "folder", if pick { 300.0 } else { 334.0 });
|
||||
form::text(ui, d, "folder", if pick { RIGHT_W - 40.0 } else { RIGHT_W - 6.0 });
|
||||
if pick && widgets::icon_button(ui, "folder-open", tl!("Choose a folder"), false, 26.0).clicked() {
|
||||
crate::picks::folder_field(app, d, "folder");
|
||||
}
|
||||
});
|
||||
form::check(ui, d, "openLocation", tl!("Open Location after Export"));
|
||||
}
|
||||
form::check(ui, d, "subfolders", tl!("Create Sub-folders"));
|
||||
ui.horizontal(|ui| {
|
||||
widgets::dim_label(ui, tl!("Folders:"));
|
||||
let current = d.str("folders");
|
||||
let at = FOLDERS.iter().position(|(id, _)| *id == current).unwrap_or(0);
|
||||
if let Some((id, _)) = widgets::dropdown(ui, "efs-folders", FOLDER_LABELS[at], &FOLDER_LABELS, 190.0).and_then(|i| FOLDERS.get(i)) {
|
||||
d.fields.insert("folders".into(), json!(id));
|
||||
d.fields.insert("subfolders".into(), json!(*id == "rows"));
|
||||
ui.add_space(22.0);
|
||||
let on = d.bool("subfolders");
|
||||
let by = d.str("subfolderBy");
|
||||
if widgets::radio(ui, tl!("Scale"), by != "format", on) {
|
||||
d.fields.insert("subfolderBy".into(), json!("scale"));
|
||||
}
|
||||
if widgets::radio(ui, tl!("Format"), by == "format", on) {
|
||||
d.fields.insert("subfolderBy".into(), json!("format"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Export Preset: apply a built-in or saved preset (its formats, settings, naming and folders),
|
||||
/// save the dialog's settings as one (Save…, then a name), or delete a saved one.
|
||||
fn presets(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
/// Export PDFs As: one file of every chosen artboard, or a file each; only for artboards (not
|
||||
/// Full Document) with a PDF row.
|
||||
fn pdf_mode(ui: &mut egui::Ui, d: &mut Dialog, artboards: bool) {
|
||||
let pdf = d
|
||||
.fields
|
||||
.get("formats")
|
||||
.and_then(Value::as_array)
|
||||
.is_some_and(|rows| rows.iter().any(|r| r["format"].as_str().and_then(fileio::format).is_some_and(|f| f.id == "pdf")));
|
||||
let on = artboards && pdf && d.str("select") != "full";
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_enabled_ui(on, |ui| widgets::dim_label(ui, tl!("Export PDFs As:")));
|
||||
let single = d.str("pdfs") == "single";
|
||||
if widgets::radio(ui, tl!("Single File"), single, on) {
|
||||
d.fields.insert("pdfs".into(), json!("single"));
|
||||
}
|
||||
if widgets::radio(ui, tl!("Multiple Files"), !single, on) {
|
||||
d.fields.insert("pdfs".into(), json!("multiple"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The format presets (the ☰ by iOS and Android): apply a built-in or saved one (its rows, their
|
||||
/// settings, the prefix and the sub-folders), save the dialog's rows as one, or delete the saved
|
||||
/// one applied last.
|
||||
fn preset_menu(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
let list: Vec<Value> = d.fields.get("__exportPresets").and_then(Value::as_array).cloned().unwrap_or_default();
|
||||
let names: Vec<&str> = list.iter().filter_map(|p| p["name"].as_str()).collect();
|
||||
let current = d.str("exportPreset");
|
||||
let saved = list.iter().any(|p| p["name"] == current && p["builtIn"] == false);
|
||||
ui.horizontal(|ui| {
|
||||
widgets::dim_label(ui, tl!("Export Preset:"));
|
||||
let labels: Vec<&str> = std::iter::once("Custom").chain(names.iter().copied()).collect();
|
||||
let at = names.iter().position(|n| *n == current).map_or(0, |i| i + 1);
|
||||
if let Some(i) = widgets::dropdown(ui, "efs-export-preset", labels[at], &labels, 150.0)
|
||||
&& let Some(preset) = i.checked_sub(1).and_then(|i| list.get(i))
|
||||
{
|
||||
apply_preset(d, preset);
|
||||
}
|
||||
if widgets::icon_button(ui, "save", tl!("Save Preset…"), false, 24.0).clicked() {
|
||||
let name = if saved { current.clone() } else { String::new() };
|
||||
d.fields.insert("__savePreset".into(), json!(name));
|
||||
}
|
||||
ui.add_enabled_ui(saved, |ui| {
|
||||
if widgets::icon_button(ui, "trash-2", tl!("Delete Preset"), false, 24.0).clicked()
|
||||
&& app.run("document.exportPresets.delete", json!({"name": current})).is_ok()
|
||||
{
|
||||
d.fields.insert("exportPreset".into(), json!(""));
|
||||
d.fields.insert("__exportPresets".into(), export_presets(app));
|
||||
let resp = widgets::icon_button(ui, "menu", tl!("Format Presets"), false, 24.0);
|
||||
let clicked = resp.clicked();
|
||||
let _ = widgets::popover(&resp, clicked, |ui| {
|
||||
for p in &list {
|
||||
let name = p["name"].as_str().unwrap_or_default();
|
||||
if widgets::menu_item_name(ui, name, true, name == current) {
|
||||
apply_preset(d, p);
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.separator();
|
||||
if widgets::menu_item(ui, tl!("Save Preset…"), true, false) {
|
||||
d.fields.insert("__savePreset".into(), json!(if saved { current.clone() } else { String::new() }));
|
||||
ui.close();
|
||||
}
|
||||
if widgets::menu_item(ui, tl!("Delete Preset"), saved, false) && app.run("document.exportPresets.delete", json!({"name": current})).is_ok() {
|
||||
d.fields.insert("exportPreset".into(), json!(""));
|
||||
d.fields.insert("__exportPresets".into(), export_presets(app));
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
if d.fields.contains_key("__savePreset") {
|
||||
ui.horizontal(|ui| {
|
||||
widgets::dim_label(ui, tl!("Name:"));
|
||||
form::text(ui, d, "__savePreset", 150.0);
|
||||
let name = d.str("__savePreset").trim().to_string();
|
||||
if ui.add_enabled(!name.is_empty(), egui::Button::new(tl!("Save"))).clicked() {
|
||||
let settings = params(app, d).unwrap_or_else(|_| json!({"formats": d.fields.get("formats").cloned().unwrap_or(json!([]))}));
|
||||
match app.run("document.exportPresets.save", json!({"name": name, "settings": settings})) {
|
||||
Ok(_) => {
|
||||
d.fields.remove("__savePreset");
|
||||
d.fields.insert("exportPreset".into(), json!(name));
|
||||
d.fields.insert("__exportPresets".into(), export_presets(app));
|
||||
}
|
||||
Err(e) => app.ui.status = e,
|
||||
}
|
||||
}
|
||||
if ui.small_button(tl!("Cancel")).clicked() {
|
||||
d.fields.remove("__savePreset");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the dialog to preset `preset` (`{name, settings}`): its formats, settings, naming and
|
||||
/// folders; what it doesn't name keeps the dialog's value.
|
||||
/// The row that names a format preset being saved (Save Preset… in the ☰ menu).
|
||||
fn save_preset_row(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
if !d.fields.contains_key("__savePreset") {
|
||||
return;
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
widgets::dim_label(ui, tl!("Name:"));
|
||||
form::text(ui, d, "__savePreset", 150.0);
|
||||
let name = d.str("__savePreset").trim().to_string();
|
||||
if ui.add_enabled(!name.is_empty(), egui::Button::new(tl!("Save"))).clicked() {
|
||||
let settings = json!({
|
||||
"formats": d.fields.get("formats").cloned().unwrap_or(json!([])),
|
||||
"settings": d.fields.get("settings").cloned().unwrap_or(json!({})),
|
||||
"prefix": d.str("prefix"),
|
||||
"folders": if !d.bool("subfolders") { "none" } else if d.str("subfolderBy") == "format" { "format" } else { "rows" },
|
||||
});
|
||||
match app.run("document.exportPresets.save", json!({"name": name, "settings": settings})) {
|
||||
Ok(_) => {
|
||||
d.fields.remove("__savePreset");
|
||||
d.fields.insert("exportPreset".into(), json!(name));
|
||||
d.fields.insert("__exportPresets".into(), export_presets(app));
|
||||
}
|
||||
Err(e) => app.ui.status = e,
|
||||
}
|
||||
}
|
||||
if ui.small_button(tl!("Cancel")).clicked() {
|
||||
d.fields.remove("__savePreset");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Set the dialog to format preset `preset` (`{name, settings}`): its rows, their settings, the
|
||||
/// prefix and the sub-folders; what it doesn't name keeps the dialog's value.
|
||||
pub(super) fn apply_preset(d: &mut Dialog, preset: &Value) {
|
||||
let st = &preset["settings"];
|
||||
if let Some(rows) = st["formats"].as_array() {
|
||||
@@ -680,127 +734,29 @@ pub(super) fn apply_preset(d: &mut Dialog, preset: &Value) {
|
||||
}
|
||||
let folders = st["folders"].as_str().or_else(|| st["subfolders"].as_bool().map(|b| if b { "rows" } else { "none" }));
|
||||
if let Some(f) = folders {
|
||||
d.fields.insert("folders".into(), json!(f));
|
||||
d.fields.insert("subfolders".into(), json!(f == "rows"));
|
||||
d.fields.insert("subfolders".into(), json!(f != "none"));
|
||||
d.fields.insert("subfolderBy".into(), json!(if f == "format" { "format" } else { "scale" }));
|
||||
}
|
||||
for k in ["name", "prefix"] {
|
||||
d.fields.insert(k.into(), json!(st[k].as_str().unwrap_or_default()));
|
||||
if let Some(prefix) = st["prefix"].as_str() {
|
||||
d.fields.insert("prefix".into(), json!(prefix));
|
||||
}
|
||||
d.fields.insert("counterStart".into(), json!(st["counterStart"].as_u64().unwrap_or(1)));
|
||||
if let Some(b) = st["includeBleed"].as_bool() {
|
||||
d.fields.insert("includeBleed".into(), json!(b));
|
||||
}
|
||||
if let Some(f) = st["folder"].as_str().filter(|f| !f.is_empty()) {
|
||||
d.fields.insert("folder".into(), json!(f));
|
||||
}
|
||||
d.fields.insert("exportPreset".into(), json!(preset["name"]));
|
||||
}
|
||||
|
||||
/// File Name: the pattern (with Insert for its tokens), the counter's start, the prefix and an
|
||||
/// example of the first file's name.
|
||||
fn naming(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
ui.horizontal(|ui| {
|
||||
widgets::dim_label(ui, tl!("File Name:"));
|
||||
// A pattern typed by hand is no longer the preset's.
|
||||
if form::text(ui, d, "name", 170.0) {
|
||||
d.fields.insert("exportPreset".into(), json!(""));
|
||||
}
|
||||
if let Some((_, token)) =
|
||||
widgets::dropdown_named(ui, "efs-name-token", Some(tl!("Insert")), tl!("Insert"), &TOKEN_LABELS, 90.0).and_then(|i| TOKENS.get(i))
|
||||
{
|
||||
let name = d.str("name");
|
||||
d.fields.insert("name".into(), json!(format!("{name}{token}")));
|
||||
}
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
widgets::dim_label(ui, tl!("Prefix:"));
|
||||
form::text(ui, d, "prefix", 110.0);
|
||||
widgets::dim_label(ui, tl!("Start at:"));
|
||||
if let Some(v) = widgets::range_field(ui, "efs-counter-start", counter_start(d) as f64, 0.0..=999_999.0, "", 0, 64.0) {
|
||||
d.fields.insert("counterStart".into(), json!(v.round().max(0.0) as u64));
|
||||
}
|
||||
});
|
||||
let example = example_name(app, d);
|
||||
if !example.is_empty() {
|
||||
widgets::dim_label(ui, &crate::i18n::fmt(tl!("Example: {name}"), &[("name", &example)]));
|
||||
}
|
||||
}
|
||||
/// What the dialog adds by the iOS and Android buttons: its format presets.
|
||||
pub(crate) type FormatMenu<'a> = &'a mut dyn FnMut(&mut egui::Ui, &mut Dialog);
|
||||
|
||||
/// The first file the export writes, relative to the export folder, as `document.exportForScreens`
|
||||
/// names it ([`fileio::screen_first_file`]); empty while the dialog's settings choose nothing to
|
||||
/// export (no format, nothing checked or selected, a bad range).
|
||||
pub(super) fn example_name(app: &VectorcraftApp, d: &Dialog) -> String {
|
||||
settings_params(app, d).ok().and_then(|(p, _)| fileio::screen_first_file(&app.session, &Value::Object(p)).ok()).unwrap_or_default()
|
||||
}
|
||||
/// The iOS and Android rows (the engine's `mobile` and `density` presets) and their labels.
|
||||
const PLATFORMS: [(&str, &str); 2] = [("mobile", "iOS"), ("density", "Android")];
|
||||
|
||||
/// The Layers tab: each top-level layer's thumbnail and checkbox (a file of its art alone),
|
||||
/// Select All and Clear.
|
||||
fn layers(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
let Some(st) = app.session.active() else { return };
|
||||
let mut on = checked_layers(app, d);
|
||||
let before = on.clone();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.small_button(tl!("Select All")).clicked() {
|
||||
on = st.doc.layers.iter().map(|l| l.id.0).collect();
|
||||
}
|
||||
if ui.small_button(tl!("Clear")).clicked() {
|
||||
on.clear();
|
||||
}
|
||||
});
|
||||
egui::ScrollArea::vertical().max_height(330.0).show(ui, |ui| {
|
||||
for l in &st.doc.layers {
|
||||
ui.horizontal(|ui| {
|
||||
let (r, _) = ui.allocate_exact_size(egui::vec2(46.0, 46.0), egui::Sense::hover());
|
||||
let mut art = vec![];
|
||||
layer_art(&st.doc, l.id, &mut art);
|
||||
// An id of its own for the thumbnail cache, away from the assets' ids.
|
||||
let a = ExportAsset { id: u64::MAX - l.id.0, name: l.display_name(), nodes: art };
|
||||
crate::panels::asset_export::paint_thumb(ui, st, &a, r);
|
||||
let checked = on.contains(&l.id.0);
|
||||
if crate::widgets::check_name(ui, &l.display_name(), checked) {
|
||||
on.retain(|id| *id != l.id.0);
|
||||
if !checked {
|
||||
on.push(l.id.0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if on != before {
|
||||
d.fields.insert("layers".into(), json!(on));
|
||||
}
|
||||
}
|
||||
|
||||
/// The Selection tab: what is selected (a thumbnail and the count), as one file or a file per
|
||||
/// object.
|
||||
fn selection(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
let Some(st) = app.session.active() else { return };
|
||||
let ids = st.selection.objects.clone();
|
||||
if ids.is_empty() {
|
||||
ui.add_space(24.0);
|
||||
widgets::dim_label(ui, tl!("Nothing is selected. Select the art to export, then open this tab again."));
|
||||
return;
|
||||
}
|
||||
let (r, _) = ui.allocate_exact_size(egui::vec2(120.0, 120.0), egui::Sense::hover());
|
||||
// The selection changes without a new revision: its ids name its thumbnail.
|
||||
let key = ids.iter().fold(0x005e_1ec7_u64, |h, id| h.rotate_left(7) ^ id.0);
|
||||
let a = ExportAsset { id: u64::MAX / 2 + (key % (u64::MAX / 4)), name: "Selection".into(), nodes: ids.clone() };
|
||||
crate::panels::asset_export::paint_thumb(ui, st, &a, r);
|
||||
let count = ids.len().to_string();
|
||||
widgets::dim_label(ui, &crate::i18n::fmt(tl!("{n} objects selected"), &[("n", &count)]));
|
||||
ui.add_space(6.0);
|
||||
let mode = d.str("selectionMode");
|
||||
if widgets::radio(ui, tl!("One File of the Selection"), mode != "each", true) {
|
||||
d.fields.insert("selectionMode".into(), json!("one"));
|
||||
}
|
||||
if widgets::radio(ui, tl!("A File per Object"), mode == "each", true) {
|
||||
d.fields.insert("selectionMode".into(), json!("each"));
|
||||
}
|
||||
}
|
||||
|
||||
/// The preset, the Format Settings gear (which sets `__settings`), the format rows (scale, suffix,
|
||||
/// format) and Add Scale, as wide as `ui` allows: the dialog's and the Asset Export panel's rows.
|
||||
pub(crate) fn formats(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
/// Formats: the iOS and Android buttons (each sets the rows that platform uses), `menu` (the
|
||||
/// dialog's format presets), the Format Settings gear (which sets `__settings`), the format rows
|
||||
/// (scale, suffix, format) and Add Scale, as wide as `ui` allows: the dialog's and the Asset
|
||||
/// Export panel's rows.
|
||||
pub(crate) fn formats(ui: &mut egui::Ui, d: &mut Dialog, mut menu: Option<FormatMenu<'_>>) {
|
||||
let mut rows: Vec<Value> = d.fields.get("formats").and_then(Value::as_array).cloned().unwrap_or_default();
|
||||
let mut edited = false;
|
||||
ui.horizontal(|ui| {
|
||||
@@ -816,19 +772,21 @@ pub(crate) fn formats(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
.filter(|f| SETTINGS.iter().any(|(id, _)| id == f));
|
||||
d.fields.insert("__settings".into(), json!(first.unwrap_or("png")));
|
||||
}
|
||||
let preset = d.str("preset");
|
||||
let labels: Vec<&str> = std::iter::once("Custom").chain(SCREEN_PRESETS.iter().map(|(_, l)| *l)).collect();
|
||||
let at = SCREEN_PRESETS.iter().position(|(id, _)| *id == preset).map_or(0, |i| i + 1);
|
||||
if let Some(i) =
|
||||
widgets::dropdown_named(ui, "efs-preset", Some(tl!("Preset:")), labels[at], &labels, (ui.available_width() - 8.0).clamp(90.0, 180.0))
|
||||
{
|
||||
let id = i.checked_sub(1).and_then(|i| SCREEN_PRESETS.get(i)).map_or("", |(id, _)| *id);
|
||||
if let Some(preset_rows) = fileio::screen_preset_rows(id) {
|
||||
if let Some(menu) = menu.as_mut() {
|
||||
menu(ui, d);
|
||||
}
|
||||
// Right to left: Android, then iOS before it.
|
||||
for (id, label) in PLATFORMS.iter().rev() {
|
||||
let on = d.str("preset") == *id;
|
||||
if ui.add(egui::Button::new(egui::RichText::new(tl!(label)).size(12.0)).selected(on).frame(on)).clicked()
|
||||
&& let Some(preset_rows) = fileio::screen_preset_rows(id)
|
||||
{
|
||||
rows = preset_rows;
|
||||
d.fields.insert("subfolders".into(), json!(true));
|
||||
d.fields.insert("subfolderBy".into(), json!("scale"));
|
||||
d.fields.insert("formats".into(), json!(rows));
|
||||
d.fields.insert("preset".into(), json!(id));
|
||||
}
|
||||
d.fields.insert("preset".into(), json!(id));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,16 +39,16 @@ fn strict(app: VectorcraftApp) -> Harness {
|
||||
h
|
||||
}
|
||||
|
||||
/// Export for Screens names its three text fields and exposes no raw widget ids.
|
||||
/// Export for Screens names its range and prefix fields and exposes no raw widget ids.
|
||||
#[test]
|
||||
fn export_for_screens_names_the_form_text_fields() {
|
||||
let mut app = app();
|
||||
app.run("file.exportForScreens", json!({})).unwrap();
|
||||
let h = strict(app);
|
||||
for name in ["File Name:", "Prefix:", "Start at:"] {
|
||||
for name in ["Range:", "Prefix:"] {
|
||||
let _ = h.get_by_role_and_label(Role::TextInput, name);
|
||||
}
|
||||
assert!(h.query_by_role_and_label(Role::TextInput, "name").is_none(), "no internal widget id");
|
||||
assert!(h.query_by_role_and_label(Role::TextInput, "range").is_none(), "no internal widget id");
|
||||
assert!(h.query_by_role_and_label(Role::TextInput, "prefix").is_none(), "no internal widget id");
|
||||
}
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ fn export_selection_collects_the_selection_and_opens_the_assets_tab() {
|
||||
assert_eq!(assets(&mut app).len(), 2);
|
||||
assert_eq!(field(&app, "assets"), json!(ids));
|
||||
let text = painted_text(&mut app, |app, ui| super::show(app, ui.ctx()));
|
||||
assert!(text.contains("Asset 1") && text.contains("Asset 2") && text.contains("Select All"), "{text}");
|
||||
// The web downloads both as one zip.
|
||||
assert!(text.contains("Asset 1") && text.contains("Asset 2") && text.contains("Clear Selection"), "{text}");
|
||||
// The web downloads both as one zip, in the 1x sub-folder Create Sub-folders makes by default.
|
||||
let r = confirm(&mut app).unwrap();
|
||||
assert_eq!(r["files"], json!(["Asset-1.png", "Asset-2.png"]));
|
||||
assert_eq!(r["files"], json!(["1x/Asset-1.png", "1x/Asset-2.png"]));
|
||||
assert_eq!(log.borrow().len(), 1);
|
||||
assert!(app.ui.dialog.is_none());
|
||||
// Nothing selected: Export Selection is off; document.exportSelection still exports in one go.
|
||||
@@ -112,8 +112,9 @@ fn the_assets_tab_exports_the_checked_assets_into_the_folder() {
|
||||
set(&mut app, "assets", json!([ids[1], 999]));
|
||||
set(&mut app, "formats", json!([{"format": "png", "scale": "2x", "suffix": "@2x"}]));
|
||||
let r = confirm(&mut app).unwrap();
|
||||
assert_eq!(r["files"], json!([format!("{folder}/Asset-2@2x.png")]));
|
||||
let png = image::load_from_memory(&std::fs::read(dir.join("Asset-2@2x.png")).unwrap()).unwrap();
|
||||
// Create Sub-folders is on by default: a folder per scale.
|
||||
assert_eq!(r["files"], json!([format!("{folder}/2x/Asset-2@2x.png")]));
|
||||
let png = image::load_from_memory(&std::fs::read(dir.join("2x").join("Asset-2@2x.png")).unwrap()).unwrap();
|
||||
assert_eq!((png.width(), png.height()), (60, 40), "the asset's art at 2x");
|
||||
assert_eq!(log.borrow().len(), 1, "Open Location shows the file");
|
||||
// The rows are remembered for the dialog and the Asset Export panel alike.
|
||||
|
||||
@@ -63,7 +63,8 @@ fn opens_on_defaults_and_draws_every_view() {
|
||||
assert_eq!((field(&app, "select"), field(&app, "range"), field(&app, "boards")), (json!("all"), json!("1-3"), json!([true, true, true])));
|
||||
assert_eq!(field(&app, "formats"), json!([{"format": "png", "scale": "1x", "suffix": ""}]));
|
||||
assert_eq!(field(&app, "folder"), json!(fileio::export_folder().unwrap_or_default()), "the Desktop, else home");
|
||||
assert_eq!((field(&app, "openLocation"), field(&app, "subfolders"), field(&app, "preset")), (json!(true), json!(false), json!("")));
|
||||
assert_eq!((field(&app, "openLocation"), field(&app, "subfolders"), field(&app, "preset")), (json!(true), json!(true), json!("")));
|
||||
assert_eq!((field(&app, "subfolderBy"), field(&app, "pdfs"), field(&app, "view")), (json!("scale"), json!("multiple"), json!("grid")));
|
||||
let settings = field(&app, "settings");
|
||||
for f in ["png", "png8", "jpg", "webp", "gif", "tiff", "svg", "pdf"] {
|
||||
assert!(settings[f].is_object(), "{f} has settings");
|
||||
@@ -128,7 +129,8 @@ fn opens_with_the_artboards_the_artboard_tool_selected_and_exports_only_them() {
|
||||
frame(&mut app);
|
||||
assert_eq!(field(&app, "boards"), json!([false, true, true, false]), "drawing the dialog keeps them");
|
||||
let r = confirm(&mut app).unwrap();
|
||||
assert_eq!(r["files"], json!(["Artboard-2.png", "Artboard-3.png"]));
|
||||
// Create Sub-folders is on by default, a folder per scale.
|
||||
assert_eq!(r["files"], json!(["1x/Artboard-2.png", "1x/Artboard-3.png"]));
|
||||
assert_eq!(log.borrow().len(), 1, "one zip");
|
||||
// With every artboard selected it opens on All.
|
||||
click(&mut app, 0, true);
|
||||
@@ -433,7 +435,7 @@ fn the_web_downloads_one_zip_or_the_file() {
|
||||
assert_eq!(name, "Untitled-1.zip");
|
||||
assert_eq!(&vectorcraft_format::base64_decode(r["dataBase64"].as_str().unwrap()).unwrap(), zip);
|
||||
}
|
||||
assert_eq!(r["files"], json!(["Artboard-1.png", "Artboard-1@2x.jpg", "Artboard-2.png", "Artboard-2@2x.jpg"]));
|
||||
assert_eq!(r["files"], json!(["1x/Artboard-1.png", "2x/Artboard-1@2x.jpg", "1x/Artboard-2.png", "2x/Artboard-2@2x.jpg"]));
|
||||
// One file downloads as itself, with the format's settings.
|
||||
log.borrow_mut().clear();
|
||||
open(&mut app);
|
||||
@@ -602,7 +604,7 @@ fn screen_format_gear(out: &egui::FullOutput, width: f32) -> egui::Pos2 {
|
||||
}
|
||||
}
|
||||
let label = out.shapes.iter().find_map(|s| label(&s.shape)).expect("painted Formats row");
|
||||
// The existing 24 pt gear is right-aligned in the 350 pt dialog or 236 pt panel.
|
||||
// The existing 24 pt gear is right-aligned in the dialog's 370 pt column or the 236 pt panel.
|
||||
egui::pos2(label.left() + width - 12.0, label.center().y)
|
||||
}
|
||||
|
||||
@@ -635,7 +637,7 @@ fn saved_tiff_alias_gear(alias: &str, asset_panel: bool) {
|
||||
};
|
||||
draw(&mut h, &mut app, vec![]);
|
||||
let out = draw(&mut h, &mut app, vec![]);
|
||||
let gear = screen_format_gear(&out, if asset_panel { 236.0 } else { 350.0 });
|
||||
let gear = screen_format_gear(&out, if asset_panel { 236.0 } else { 370.0 });
|
||||
draw(&mut h, &mut app, vec![egui::Event::PointerMoved(gear)]);
|
||||
for pressed in [true, false] {
|
||||
draw(
|
||||
@@ -707,11 +709,12 @@ fn tiff_dropdown_exports_scaled_artboards_with_options_and_reopens() {
|
||||
// Begin with an existing PNG row; TIFF must be chosen from the actual popup.
|
||||
set(&mut app, "formats", json!([{"format": "png", "scale": "2x", "suffix": "@2x"}]));
|
||||
set(&mut app, "prefix", json!("proof-"));
|
||||
set(&mut app, "subfolders", json!(false));
|
||||
let mut h = ScreensFrame::new();
|
||||
h.choose(&mut app, "PNG", "TIFF");
|
||||
assert_eq!(field(&app, "formats"), json!([{"format": "tiff", "scale": "2x", "suffix": "@2x"}]));
|
||||
// Leave only the first of two boards checked.
|
||||
h.click_label(&mut app, "Artboard 2");
|
||||
h.click_label(&mut app, "2 Artboard 2");
|
||||
assert_eq!(field(&app, "boards"), json!([true, false]));
|
||||
assert_eq!(field(&app, "range"), json!("1"));
|
||||
|
||||
@@ -766,168 +769,114 @@ fn tiff_dropdown_exports_scaled_artboards_with_options_and_reopens() {
|
||||
/// name, a prefix with characters Windows does not allow, a range, a Windows device name as an item
|
||||
/// folder, the rows' folders and sizes, Create Sub-folders, a vector row's size suffix, the counter
|
||||
/// typed as text, and the Layers and Selection tabs.
|
||||
/// The dialog is laid out as Illustrator's: two tabs (Artboards, Assets), Clear Selection and the
|
||||
/// prefix under the thumbnails, Create Sub-folders by scale or format, Export PDFs As, the iOS and
|
||||
/// Android rows, and how many items and files the export takes; no naming, layer or selection
|
||||
/// pages, presets bar or folders list.
|
||||
#[test]
|
||||
fn the_example_names_the_first_file_the_export_writes() {
|
||||
fn the_dialog_is_laid_out_as_illustrators() {
|
||||
let (mut app, _) = app(3, false);
|
||||
app.run("artboard.setProps", json!({"index": 2, "name": "CON"})).unwrap();
|
||||
app.run("select.all", json!({})).unwrap();
|
||||
let dir = temp_folder("example");
|
||||
let folder = dir.to_string_lossy().to_string();
|
||||
let png = json!([{"format": "png", "scale": "1x", "suffix": ""}]);
|
||||
let cases: Vec<Vec<(&str, Value)>> = vec![
|
||||
vec![],
|
||||
vec![("prefix", json!("a?b"))],
|
||||
vec![("select", json!("range")), ("range", json!("2-3"))],
|
||||
vec![("select", json!("range")), ("range", json!("3")), ("folders", json!("item"))],
|
||||
vec![("folders", json!("rows")), ("formats", json!([{"format": "png", "scale": "144ppi", "suffix": ""}]))],
|
||||
vec![("subfolders", json!(true))],
|
||||
vec![("folders", json!("rows")), ("formats", json!([{"format": "png", "scale": "2x", "suffix": "@2x", "folder": "retina"}]))],
|
||||
vec![("formats", json!([{"format": "svg", "scale": "2x", "suffix": "@2x"}]))],
|
||||
vec![("name", json!("<scale>-####")), ("counterStart", json!("7")), ("formats", json!([{"format": "png", "scale": "144ppi", "suffix": ""}]))],
|
||||
vec![("tab", json!("layers"))],
|
||||
vec![("tab", json!("selection")), ("selectionMode", json!("each"))],
|
||||
];
|
||||
let mut wrong = vec![];
|
||||
for case in cases {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
open(&mut app);
|
||||
let defaults = [
|
||||
("folder", json!(folder)),
|
||||
("tab", json!("artboards")),
|
||||
("select", json!("all")),
|
||||
("prefix", json!("")),
|
||||
("name", json!("")),
|
||||
("counterStart", json!(1)),
|
||||
("folders", json!("none")),
|
||||
("subfolders", json!(false)),
|
||||
("formats", png.clone()),
|
||||
];
|
||||
for (k, v) in defaults.into_iter().chain(case.iter().cloned()) {
|
||||
set(&mut app, k, v);
|
||||
}
|
||||
frame(&mut app);
|
||||
let example = export_for_screens::example_name(&app, app.ui.dialog.as_ref().unwrap());
|
||||
let r = confirm(&mut app).unwrap();
|
||||
let first = r["files"][0].as_str().and_then(|f| f.strip_prefix(&format!("{folder}/"))).unwrap_or_default().to_string();
|
||||
if example != first {
|
||||
wrong.push(format!("{case:?}: example {example:?}, written {first:?}"));
|
||||
}
|
||||
open(&mut app);
|
||||
let text = crate::tests_labels::shapes_text(&ScreensFrame::new().settle(&mut app));
|
||||
for label in [
|
||||
"Artboards",
|
||||
"Assets",
|
||||
"Select:",
|
||||
"Full Document",
|
||||
"Include Bleed",
|
||||
"Export to:",
|
||||
"Open Location after Export",
|
||||
"Create Sub-folders",
|
||||
"Scale",
|
||||
"Format",
|
||||
"Export PDFs As:",
|
||||
"Single File",
|
||||
"Multiple Files",
|
||||
"Formats:",
|
||||
"iOS",
|
||||
"Android",
|
||||
"+ Add Scale",
|
||||
"Clear Selection",
|
||||
"Prefix:",
|
||||
"Selected: 3, Total number of files to export: 3",
|
||||
] {
|
||||
assert!(text.lines().any(|l| l.trim() == label), "{label} missing:\n{text}");
|
||||
}
|
||||
for gone in ["Layers", "Selection", "Export Preset:", "File Name:", "Folders:", "Start at:"] {
|
||||
assert!(!text.lines().any(|l| l.trim() == gone), "{gone} shown:\n{text}");
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
assert!(wrong.is_empty(), "{wrong:#?}");
|
||||
}
|
||||
|
||||
/// The Example line shows before a folder is chosen and is left out while the settings choose
|
||||
/// nothing to export (the Assets tab with no asset checked); Start at shows the counter the export
|
||||
/// uses when it was set as text.
|
||||
/// iOS and Android set the rows those platforms use (in sub-folders); Clear Selection unchecks
|
||||
/// every artboard, so nothing is exported until one is checked again.
|
||||
#[test]
|
||||
fn the_example_needs_no_folder_and_start_at_shows_the_counter_used() {
|
||||
let (mut app, _) = app(1, false);
|
||||
fn ios_android_and_clear_selection() {
|
||||
let (mut app, _) = app(2, false);
|
||||
open(&mut app);
|
||||
for (k, v) in [("folder", json!("")), ("name", json!("####")), ("counterStart", json!("7"))] {
|
||||
set(&mut app, "subfolders", json!(false));
|
||||
let mut h = ScreensFrame::new();
|
||||
h.click_label(&mut app, "iOS");
|
||||
let scales: Vec<Value> = field(&app, "formats").as_array().unwrap().iter().map(|r| r["scale"].clone()).collect();
|
||||
assert_eq!((field(&app, "preset"), field(&app, "subfolders")), (json!("mobile"), json!(true)));
|
||||
assert_eq!(scales, [json!("1x"), json!("2x"), json!("3x")]);
|
||||
h.click_label(&mut app, "Android");
|
||||
assert_eq!((field(&app, "preset"), field(&app, "formats").as_array().unwrap().len()), (json!("density"), 6));
|
||||
h.click_label(&mut app, "Clear Selection");
|
||||
assert_eq!((field(&app, "boards"), field(&app, "select")), (json!([false, false]), json!("range")));
|
||||
let text = crate::tests_labels::shapes_text(&h.settle(&mut app));
|
||||
assert!(text.contains("Selected: 0, Total number of files to export: 0"), "{text}");
|
||||
set(&mut app, "folder", json!(temp_folder("cleared").to_string_lossy()));
|
||||
assert!(confirm(&mut app).is_err(), "nothing to export");
|
||||
}
|
||||
|
||||
/// Create Sub-folders sorts the files into a folder per scale or per format; Export PDFs As ›
|
||||
/// Single file writes the chosen artboards as one PDF named after the document.
|
||||
#[test]
|
||||
fn sub_folders_and_single_file_pdfs() {
|
||||
let (mut app, _) = app(3, false);
|
||||
let dir = temp_folder("subfolders");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let folder = dir.to_string_lossy().to_string();
|
||||
let title = app.session.active().unwrap().doc.title.clone();
|
||||
let rows = json!([
|
||||
{"format": "png", "scale": "1x", "suffix": ""},
|
||||
{"format": "png", "scale": "2x", "suffix": "@2x"},
|
||||
{"format": "pdf", "scale": "1x", "suffix": ""}
|
||||
]);
|
||||
let files = |app: &mut VectorcraftApp, extra: &[(&str, Value)]| -> Vec<String> {
|
||||
open(app);
|
||||
let base = [("folder", json!(folder)), ("select", json!("range")), ("range", json!("1-2")), ("formats", rows.clone())];
|
||||
for (k, v) in base.into_iter().chain(extra.iter().cloned()) {
|
||||
set(app, k, v);
|
||||
}
|
||||
frame(app);
|
||||
let r = confirm(app).unwrap();
|
||||
r["files"].as_array().unwrap().iter().map(|f| f.as_str().unwrap().strip_prefix(&format!("{folder}/")).unwrap().to_string()).collect()
|
||||
};
|
||||
let by_scale = files(&mut app, &[("subfolders", json!(true)), ("subfolderBy", json!("scale"))]);
|
||||
assert_eq!(&by_scale[..2], ["1x/Artboard-1.png", "2x/Artboard-1@2x.png"]);
|
||||
let by_format = files(&mut app, &[("subfolders", json!(true)), ("subfolderBy", json!("format")), ("pdfs", json!("single"))]);
|
||||
let pdf = format!("pdf/{title}.pdf");
|
||||
assert_eq!(by_format, ["png/Artboard-1.png", "png/Artboard-1@2x.png", pdf.as_str(), "png/Artboard-2.png", "png/Artboard-2@2x.png"]);
|
||||
let flat = files(&mut app, &[("subfolders", json!(false)), ("pdfs", json!("multiple"))]);
|
||||
assert_eq!(flat.len(), 6);
|
||||
assert!(flat.contains(&"Artboard-2.pdf".to_string()), "{flat:?}");
|
||||
// The status line counts the files Single file writes.
|
||||
open(&mut app);
|
||||
for (k, v) in [("select", json!("range")), ("range", json!("1-2")), ("formats", rows.clone()), ("pdfs", json!("single"))] {
|
||||
set(&mut app, k, v);
|
||||
}
|
||||
let mut h = ScreensFrame::new();
|
||||
let text = crate::tests_labels::shapes_text(&h.settle(&mut app));
|
||||
assert!(text.lines().any(|l| l == "Example: 0007.png"), "{text}");
|
||||
assert!(text.lines().any(|l| l.trim() == "7"), "{text}");
|
||||
set(&mut app, "tab", json!("assets"));
|
||||
let text = crate::tests_labels::shapes_text(&h.settle(&mut app));
|
||||
assert!(!text.contains("Example:"), "{text}");
|
||||
}
|
||||
|
||||
/// Batch exporting (#1935): a file name pattern and a folder per format, the Layers and Selection
|
||||
/// tabs, and export presets applied and saved from the dialog.
|
||||
#[test]
|
||||
fn batch_naming_folders_layers_selection_and_presets() {
|
||||
let (mut app, _) = app(3, false);
|
||||
let dir = temp_folder("batch");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let folder = dir.to_string_lossy().to_string();
|
||||
open(&mut app);
|
||||
assert_eq!(field(&app, "folders"), json!("none"));
|
||||
// Every tab draws; the OK button says what is exported.
|
||||
for (tab, ok) in [("layers", "Export Layer"), ("selection", "Export Selection"), ("assets", "Export Asset"), ("artboards", "Export Artboard")] {
|
||||
set(&mut app, "tab", json!(tab));
|
||||
frame(&mut app);
|
||||
assert_eq!((spec(export_for_screens::KIND).ok_label.unwrap())(&app), ok);
|
||||
}
|
||||
// Artboards 1–2 as SVG and print PDF, named by pattern, a folder per format.
|
||||
set(&mut app, "folder", json!(folder));
|
||||
set(&mut app, "select", json!("range"));
|
||||
set(&mut app, "range", json!("1-2"));
|
||||
set(&mut app, "name", json!("ACME-<name>-##"));
|
||||
set(&mut app, "folders", json!("format"));
|
||||
set(
|
||||
&mut app,
|
||||
"formats",
|
||||
json!([{"format": "svg", "scale": "1x", "suffix": ""}, {"format": "pdf", "scale": "1x", "suffix": "-print", "marks": {"trim": true}}]),
|
||||
);
|
||||
frame(&mut app);
|
||||
{
|
||||
let d = app.ui.dialog.as_ref().unwrap();
|
||||
assert_eq!(export_for_screens::example_name(&app, d), "svg/ACME-Artboard 1-01.svg");
|
||||
}
|
||||
let r = confirm(&mut app).unwrap();
|
||||
let files: Vec<String> = r["files"].as_array().unwrap().iter().map(|f| f.as_str().unwrap().to_string()).collect();
|
||||
assert_eq!(
|
||||
files,
|
||||
[
|
||||
format!("{folder}/svg/ACME-Artboard 1-01.svg"),
|
||||
format!("{folder}/pdf/ACME-Artboard 1-01-print.pdf"),
|
||||
format!("{folder}/svg/ACME-Artboard 2-02.svg"),
|
||||
format!("{folder}/pdf/ACME-Artboard 2-02-print.pdf")
|
||||
]
|
||||
);
|
||||
// It reopens on the pattern and the folders.
|
||||
open(&mut app);
|
||||
assert_eq!((field(&app, "name"), field(&app, "folders")), (json!("ACME-<name>-##"), json!("format")));
|
||||
|
||||
// The Layers tab: one file of a layer's art.
|
||||
let layer = app.session.active().unwrap().doc.layers[0].id.0;
|
||||
set(&mut app, "folder", json!(folder));
|
||||
set(&mut app, "tab", json!("layers"));
|
||||
set(&mut app, "layers", json!([layer]));
|
||||
set(&mut app, "name", json!(""));
|
||||
set(&mut app, "folders", json!("none"));
|
||||
set(&mut app, "formats", json!([{"format": "png", "scale": "1x", "suffix": ""}]));
|
||||
let r = confirm(&mut app).unwrap();
|
||||
assert_eq!(r["files"][0], json!(format!("{folder}/Layer-1.png")));
|
||||
|
||||
// The Selection tab: nothing selected keeps the dialog open; a file per selected object.
|
||||
open(&mut app);
|
||||
app.run("select.none", json!({})).unwrap();
|
||||
set(&mut app, "folder", json!(folder));
|
||||
set(&mut app, "tab", json!("selection"));
|
||||
frame(&mut app);
|
||||
assert!(confirm(&mut app).is_err());
|
||||
app.run("select.all", json!({})).unwrap();
|
||||
set(&mut app, "selectionMode", json!("each"));
|
||||
set(&mut app, "name", json!("piece-#"));
|
||||
set(&mut app, "formats", json!([{"format": "svg", "scale": "1x", "suffix": ""}]));
|
||||
frame(&mut app);
|
||||
let r = confirm(&mut app).unwrap();
|
||||
assert_eq!(r["files"][0], json!(format!("{folder}/piece-1.svg")));
|
||||
|
||||
// Presets: the Logo Package sets the formats, naming and folders; the dialog saves its own.
|
||||
open(&mut app);
|
||||
let text = crate::tests_labels::shapes_text(&ScreensFrame::new().settle(&mut app));
|
||||
assert!(text.contains("Selected: 2, Total number of files to export: 5"), "{text}");
|
||||
// A format preset sets the rows, their settings and the sub-folders, not a prefix it lacks.
|
||||
set(&mut app, "prefix", json!("x-"));
|
||||
let presets = field(&app, "__exportPresets");
|
||||
let logo = presets.as_array().unwrap().iter().find(|p| p["name"] == "Logo Package").unwrap().clone();
|
||||
export_for_screens::apply_preset(app.ui.dialog.as_mut().unwrap(), &logo);
|
||||
assert_eq!(field(&app, "formats").as_array().unwrap().len(), 5);
|
||||
assert_eq!(
|
||||
(field(&app, "folders"), field(&app, "name"), field(&app, "exportPreset")),
|
||||
(json!("format"), json!("<filename>-<name>"), json!("Logo Package"))
|
||||
(field(&app, "subfolders"), field(&app, "subfolderBy"), field(&app, "exportPreset")),
|
||||
(json!(true), json!("format"), json!("Logo Package"))
|
||||
);
|
||||
frame(&mut app);
|
||||
set(&mut app, "folder", json!(folder));
|
||||
set(&mut app, "__savePreset", json!("Client Pack"));
|
||||
frame(&mut app);
|
||||
let saved = app
|
||||
.run("document.exportPresets.save", json!({"name": "Client Pack", "settings": {"formats": field(&app, "formats"), "folders": "item"}}))
|
||||
.unwrap();
|
||||
assert_eq!(saved["name"], "Client Pack");
|
||||
let list = export_for_screens::export_presets(&mut app);
|
||||
assert!(list.as_array().unwrap().iter().any(|p| p["name"] == "Client Pack" && p["builtIn"] == false));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -649,6 +649,13 @@ font-technology Variable Променливи
|
||||
Create Report Създаване на отчет
|
||||
Create Slice Създаване на отрязък
|
||||
Create Sub-folders Създаване на подпапки
|
||||
Clear Selection Изчистване на избора
|
||||
Export PDFs As: Експортиране на PDF като:
|
||||
Single File Един файл
|
||||
Multiple Files Няколко файла
|
||||
Format Presets Предварителни настройки на формата
|
||||
Grid View Изглед на мрежа
|
||||
Selected: {n}, Total number of files to export: {m} Избрани: {n}, общ брой файлове за експортиране: {m}
|
||||
Create Text Създаване на текст
|
||||
Create Trim Marks Създаване на режещи знаци
|
||||
Created Създадено
|
||||
@@ -3952,6 +3959,7 @@ axis Both И двете
|
||||
@msg cannot split an open path at its end point отворен път не може да бъде разделен в крайната му точка
|
||||
@msg closing needs three anchors затварянето изисква три опорни точки
|
||||
@msg check an asset to export изберете актив за експортиране
|
||||
@msg check an artboard to export изберете монтажна област за експортиране
|
||||
@msg check a layer to export отметнете слой за експортиране
|
||||
@msg select the art to export first първо изберете графиката за експортиране
|
||||
@msg choice must be "merge" or "add", not `{choice}` изборът трябва да бъде "merge" или "add", а не `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2228,6 +2228,13 @@ lighting Light Svjetlo
|
||||
Use Artboards Koristi radne ploče
|
||||
Choose a folder Odaberite folder
|
||||
Create Sub-folders Kreiraj podfoldere
|
||||
Clear Selection Poništi odabir
|
||||
Export PDFs As: Izvezi PDF kao:
|
||||
Single File Jedna datoteka
|
||||
Multiple Files Više datoteka
|
||||
Format Presets Postavke formata
|
||||
Grid View Prikaz mreže
|
||||
Selected: {n}, Total number of files to export: {m} Odabrano: {n}, ukupan broj datoteka za izvoz: {m}
|
||||
Export Asset Izvezi resurs
|
||||
Export to: Izvezi u:
|
||||
Format Settings Postavke formata
|
||||
@@ -4339,6 +4346,7 @@ imageTrace Outline Obris
|
||||
@msg cannot split an open path at its end point nije moguće razdvojiti otvorenu putanju na njenoj krajnjoj tački
|
||||
@msg closing needs three anchors zatvaranje zahtijeva tri sidra
|
||||
@msg check an asset to export označite resurs za izvoz
|
||||
@msg check an artboard to export označite radnu površinu za izvoz
|
||||
@msg check a layer to export označite sloj za izvoz
|
||||
@msg select the art to export first prvo odaberite crtež za izvoz
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice mora biti "merge" ili "add", a ne `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -918,6 +918,13 @@
|
||||
Create Slice from Selection Crea un tall a partir de la selecció
|
||||
Create Slices from Guides Crea talls a partir de guies
|
||||
Create Sub-folders Crea subcarpetes
|
||||
Clear Selection Esborra la selecció
|
||||
Export PDFs As: Exporta els PDF com a:
|
||||
Single File Un sol fitxer
|
||||
Multiple Files Diversos fitxers
|
||||
Format Presets Preajustos de format
|
||||
Grid View Vista de quadrícula
|
||||
Selected: {n}, Total number of files to export: {m} Seleccionats: {n}, nombre total de fitxers per exportar: {m}
|
||||
Create Text Crea text
|
||||
Create Trim Marks Crea marques de retall
|
||||
Create: Crea:
|
||||
@@ -4885,6 +4892,7 @@
|
||||
@msg cannot split an open path at its end point no es pot dividir un traçat obert al seu punt final
|
||||
@msg check a layer to export marqueu una capa per exportar
|
||||
@msg check an asset to export marqueu un recurs per exportar
|
||||
@msg check an artboard to export marqueu una mesa de treball per exportar
|
||||
@msg choice must be "merge" or "add", not `{choice}` l’opció ha de ser "merge" o "add", no `{choice}`
|
||||
@msg Choose a circle or a perfect arc Trieu un cercle o un arc perfecte
|
||||
@msg choose a destination folder trieu una carpeta de destinació
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2257,6 +2257,13 @@ lighting Light Světlo
|
||||
Use Artboards Použít pracovní plochy
|
||||
Choose a folder Vyberte složku
|
||||
Create Sub-folders Vytvořit podsložky
|
||||
Clear Selection Vymazat výběr
|
||||
Export PDFs As: Exportovat PDF jako:
|
||||
Single File Jeden soubor
|
||||
Multiple Files Více souborů
|
||||
Format Presets Přednastavení formátů
|
||||
Grid View Zobrazení mřížky
|
||||
Selected: {n}, Total number of files to export: {m} Vybráno: {n}, celkový počet souborů k exportu: {m}
|
||||
Export Asset Exportovat prvek
|
||||
Export to: Exportovat do:
|
||||
Format Settings Nastavení formátu
|
||||
@@ -4375,6 +4382,7 @@ imageTrace Outline Obrys
|
||||
@msg cannot split an open path at its end point otevřenou cestu nelze rozdělit v jejím koncovém bodu
|
||||
@msg closing needs three anchors uzavření vyžaduje tři kotevní body
|
||||
@msg check an asset to export zaškrtněte prvek k exportu
|
||||
@msg check an artboard to export zaškrtněte kreslicí plátno k exportu
|
||||
@msg check a layer to export zaškrtněte vrstvu k exportu
|
||||
@msg select the art to export first nejprve vyberte kresbu k exportu
|
||||
@msg choice must be "merge" or "add", not `{choice}` volba musí být "merge" nebo "add", ne `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2061,6 +2061,13 @@ lighting Light Lys
|
||||
Use Artboards Brug tegnebrætter
|
||||
Choose a folder Vælg en mappe
|
||||
Create Sub-folders Opret undermapper
|
||||
Clear Selection Ryd markering
|
||||
Export PDFs As: Eksportér PDF'er som:
|
||||
Single File Én fil
|
||||
Multiple Files Flere filer
|
||||
Format Presets Formatforudindstillinger
|
||||
Grid View Gittervisning
|
||||
Selected: {n}, Total number of files to export: {m} Valgt: {n}, samlet antal filer, der eksporteres: {m}
|
||||
Export Asset Eksportér aktiv
|
||||
Export to: Eksportér til:
|
||||
Format Settings Formatindstillinger
|
||||
@@ -4067,6 +4074,7 @@ imageTrace Outline Kontur
|
||||
@msg cannot split an open path at its end point kan ikke opdele en åben kurve i dens endepunkt
|
||||
@msg closing needs three anchors lukning kræver tre ankerpunkter
|
||||
@msg check an asset to export afkryds et aktiv, der skal eksporteres
|
||||
@msg check an artboard to export afkryds en tegneflade, der skal eksporteres
|
||||
@msg choice must be "merge" or "add", not `{choice}` valget skal være "merge" eller "add", ikke `{choice}`
|
||||
@msg choose a direction to divide in vælg en retning at opdele i
|
||||
@msg choose a folder to export to vælg en mappe at eksportere til
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -642,6 +642,13 @@ font-technology Variable Variabel
|
||||
Create Report Bericht erstellen
|
||||
Create Slice Slice erstellen
|
||||
Create Sub-folders Unterordner erstellen
|
||||
Clear Selection Auswahl aufheben
|
||||
Export PDFs As: PDFs exportieren als:
|
||||
Single File Einzelne Datei
|
||||
Multiple Files Mehrere Dateien
|
||||
Format Presets Formatvorgaben
|
||||
Grid View Rasteransicht
|
||||
Selected: {n}, Total number of files to export: {m} Ausgewählt: {n}, Gesamtzahl der zu exportierenden Dateien: {m}
|
||||
Create Text Text erstellen
|
||||
Create Trim Marks Schnittmarken erstellen
|
||||
Created Erstellt
|
||||
@@ -3988,6 +3995,7 @@ theme Appearance Erscheinungsbild
|
||||
@msg cannot split an open path at its end point ein offener Pfad kann nicht an seinem Endpunkt geteilt werden
|
||||
@msg closing needs three anchors zum Schließen sind drei Ankerpunkte nötig
|
||||
@msg check an asset to export markieren Sie ein Asset für den Export
|
||||
@msg check an artboard to export markieren Sie eine Zeichenfläche für den Export
|
||||
@msg check a layer to export wählen Sie eine Ebene für den Export
|
||||
@msg select the art to export first wählen Sie zuerst das zu exportierende Bildmaterial aus
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice muss "merge" oder "add" sein, nicht `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -623,6 +623,13 @@ font-technology Variable Μεταβλητές
|
||||
Create Report Δημιουργία αναφοράς
|
||||
Create Slice Δημιουργία τομής
|
||||
Create Sub-folders Δημιουργία υποφακέλων
|
||||
Clear Selection Απαλοιφή επιλογής
|
||||
Export PDFs As: Εξαγωγή PDF ως:
|
||||
Single File Ένα αρχείο
|
||||
Multiple Files Πολλά αρχεία
|
||||
Format Presets Προεπιλογές μορφής
|
||||
Grid View Προβολή πλέγματος
|
||||
Selected: {n}, Total number of files to export: {m} Επιλεγμένα: {n}, συνολικός αριθμός αρχείων για εξαγωγή: {m}
|
||||
Create Text Δημιουργία κειμένου
|
||||
Create Trim Marks Δημιουργία σημαδιών ξακρίσματος
|
||||
Created Δημιουργήθηκε
|
||||
@@ -4028,6 +4035,7 @@ theme Appearance Εμφάνιση
|
||||
@msg cannot split an open path at its end point δεν είναι δυνατή η διαίρεση ανοιχτής διαδρομής στο τελικό σημείο της
|
||||
@msg closing needs three anchors το κλείσιμο απαιτεί τρία σημεία αγκύρωσης
|
||||
@msg check an asset to export επιλέξτε στοιχείο για εξαγωγή
|
||||
@msg check an artboard to export επιλέξτε πινακίδα σχεδίασης για εξαγωγή
|
||||
@msg check a layer to export επιλέξτε ένα επίπεδο για εξαγωγή
|
||||
@msg select the art to export first επιλέξτε πρώτα το γραφικό για εξαγωγή
|
||||
@msg choice must be "merge" or "add", not `{choice}` η επιλογή πρέπει να είναι "merge" ή "add", όχι `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 164 and column 5.
|
@@ -663,6 +663,13 @@ font-technology Variable Variables
|
||||
Create Report Crear informe
|
||||
Create Slice Crear sector
|
||||
Create Sub-folders Crear subcarpetas
|
||||
Clear Selection Borrar selección
|
||||
Export PDFs As: Exportar PDF como:
|
||||
Single File Un solo archivo
|
||||
Multiple Files Varios archivos
|
||||
Format Presets Preajustes de formato
|
||||
Grid View Vista de cuadrícula
|
||||
Selected: {n}, Total number of files to export: {m} Seleccionados: {n}, número total de archivos para exportar: {m}
|
||||
Create Text Crear texto
|
||||
Create Trim Marks Crear marcas de recorte
|
||||
Created Creado
|
||||
@@ -4068,6 +4075,7 @@ axis Both Ambos
|
||||
@msg cannot split an open path at its end point no se puede dividir un trazado abierto en su punto final
|
||||
@msg closing needs three anchors cerrar necesita tres puntos de ancla
|
||||
@msg check an asset to export marca un recurso para exportar
|
||||
@msg check an artboard to export marca una mesa de trabajo para exportar
|
||||
@msg check a layer to export marque una capa para exportar
|
||||
@msg select the art to export first seleccione primero la ilustración que desea exportar
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice debe ser "merge" o "add", no `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2213,6 +2213,13 @@ lighting Light Valo
|
||||
Use Artboards Käytä piirtoalustoja
|
||||
Choose a folder Valitse kansio
|
||||
Create Sub-folders Luo alikansiot
|
||||
Clear Selection Tyhjennä valinta
|
||||
Export PDFs As: Vie PDF-tiedostot muodossa:
|
||||
Single File Yksi tiedosto
|
||||
Multiple Files Useita tiedostoja
|
||||
Format Presets Muotoesiasetukset
|
||||
Grid View Ruudukkonäkymä
|
||||
Selected: {n}, Total number of files to export: {m} Valittu: {n}, vietävien tiedostojen määrä yhteensä: {m}
|
||||
Export Asset Vie resurssi
|
||||
Export to: Vie kohteeseen:
|
||||
Format Settings Muotoasetukset
|
||||
@@ -4284,6 +4291,7 @@ imageTrace Outline Ääriviiva
|
||||
@msg cannot split an open path at its end point avointa polkua ei voi jakaa sen päätepisteestä
|
||||
@msg closing needs three anchors sulkeminen vaatii kolme ankkuria
|
||||
@msg check an asset to export valitse vietävä resurssi
|
||||
@msg check an artboard to export valitse vietävä piirtoalue
|
||||
@msg check a layer to export valitse vietävä taso
|
||||
@msg select the art to export first valitse ensin vietävä kuvitus
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice-arvon on oltava "merge" tai "add", ei `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -665,6 +665,13 @@ font-technology Variable Variables
|
||||
Create Report Créer un rapport
|
||||
Create Slice Créer une tranche
|
||||
Create Sub-folders Créer des sous-dossiers
|
||||
Clear Selection Effacer la sélection
|
||||
Export PDFs As: Exporter les PDF sous :
|
||||
Single File Un seul fichier
|
||||
Multiple Files Plusieurs fichiers
|
||||
Format Presets Paramètres prédéfinis de format
|
||||
Grid View Vue en grille
|
||||
Selected: {n}, Total number of files to export: {m} Sélectionnés : {n}, nombre total de fichiers à exporter : {m}
|
||||
Create Text Créer du texte
|
||||
Create Trim Marks Créer des traits de coupe
|
||||
Created Création
|
||||
@@ -4065,6 +4072,7 @@ axis Both Les deux
|
||||
@msg cannot split an open path at its end point impossible de scinder un tracé ouvert à son extrémité
|
||||
@msg closing needs three anchors la fermeture nécessite trois points d’ancrage
|
||||
@msg check an asset to export cochez une ressource à exporter
|
||||
@msg check an artboard to export cochez un plan de travail à exporter
|
||||
@msg check a layer to export cochez un calque à exporter
|
||||
@msg select the art to export first sélectionnez d’abord l’illustration à exporter
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice doit valoir "merge" ou "add", pas `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -859,6 +859,13 @@ font-technology Variable Változó
|
||||
Create Report Jelentés készítése
|
||||
Create Slice Szelet létrehozása
|
||||
Create Sub-folders Almappák létrehozása
|
||||
Clear Selection Kijelölés törlése
|
||||
Export PDFs As: PDF-ek exportálása:
|
||||
Single File Egy fájlba
|
||||
Multiple Files Több fájlba
|
||||
Format Presets Formátum-előbeállítások
|
||||
Grid View Rácsnézet
|
||||
Selected: {n}, Total number of files to export: {m} Kijelölve: {n}, exportálandó fájlok száma összesen: {m}
|
||||
Create Text Szöveg létrehozása
|
||||
Create Trim Marks Vágójelek létrehozása
|
||||
Create: Létrehozás:
|
||||
@@ -4624,6 +4631,7 @@ imageTrace Outline Körvonal
|
||||
@msg cannot read PDF: {0} a PDF nem olvasható: {0}
|
||||
@msg cannot split an open path at its end point nyitott útvonal a végpontjánál nem vágható ketté
|
||||
@msg check an asset to export jelölj be egy exportálandó elemet
|
||||
@msg check an artboard to export jelölj be egy exportálandó rajztáblát
|
||||
@msg check a layer to export jelöljön be egy exportálandó réteget
|
||||
@msg select the art to export first először jelölje ki az exportálandó grafikát
|
||||
@msg choice must be "merge" or "add", not `{choice}` a choice értéke "merge" vagy "add" lehet, ez nem az: `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2061,6 +2061,13 @@ lighting Light Cahaya
|
||||
Use Artboards Gunakan artboard
|
||||
Choose a folder Pilih folder
|
||||
Create Sub-folders Buat subfolder
|
||||
Clear Selection Hapus pilihan
|
||||
Export PDFs As: Ekspor PDF sebagai:
|
||||
Single File Satu file
|
||||
Multiple Files Beberapa file
|
||||
Format Presets Preset format
|
||||
Grid View Tampilan kisi
|
||||
Selected: {n}, Total number of files to export: {m} Dipilih: {n}, jumlah total file yang akan diekspor: {m}
|
||||
Export Asset Ekspor aset
|
||||
Export to: Ekspor ke:
|
||||
Format Settings Pengaturan format
|
||||
@@ -4067,6 +4074,7 @@ imageTrace Outline Kontur
|
||||
@msg cannot split an open path at its end point tidak dapat membelah jalur terbuka pada titik ujungnya
|
||||
@msg closing needs three anchors penutupan memerlukan tiga jangkar
|
||||
@msg check an asset to export centang aset yang akan diekspor
|
||||
@msg check an artboard to export centang artboard yang akan diekspor
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice harus "merge" atau "add", bukan `{choice}`
|
||||
@msg choose a direction to divide in pilih arah untuk membagi
|
||||
@msg choose a folder to export to pilih folder tujuan ekspor
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2175 and column 5.
|
@@ -663,6 +663,13 @@ font-technology Variable Variabili
|
||||
Create Report Crea rapporto
|
||||
Create Slice Crea sezione
|
||||
Create Sub-folders Crea sottocartelle
|
||||
Clear Selection Cancella selezione
|
||||
Export PDFs As: Esporta PDF come:
|
||||
Single File File singolo
|
||||
Multiple Files Più file
|
||||
Format Presets Predefiniti formato
|
||||
Grid View Vista griglia
|
||||
Selected: {n}, Total number of files to export: {m} Selezionati: {n}, numero totale di file da esportare: {m}
|
||||
Create Text Crea testo
|
||||
Create Trim Marks Crea segni di taglio
|
||||
Created Creato
|
||||
@@ -4108,6 +4115,7 @@ axis Both Entrambi
|
||||
@msg cannot split an open path at its end point impossibile dividere un tracciato aperto nel suo punto finale
|
||||
@msg closing needs three anchors per chiudere servono tre punti di ancoraggio
|
||||
@msg check an asset to export spunta una risorsa da esportare
|
||||
@msg check an artboard to export spunta una tavola da disegno da esportare
|
||||
@msg check a layer to export seleziona un livello da esportare
|
||||
@msg select the art to export first seleziona prima la grafica da esportare
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice deve essere "merge" o "add", non `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1231,6 +1231,13 @@ font-technology Variable バリアブル
|
||||
Create Path パスを作成
|
||||
Create Slice スライスを作成
|
||||
Create Sub-folders サブフォルダーを作成
|
||||
Clear Selection 選択を解除
|
||||
Export PDFs As: PDF の書き出し形式:
|
||||
Single File 単一ファイル
|
||||
Multiple Files 複数ファイル
|
||||
Format Presets 形式プリセット
|
||||
Grid View グリッド表示
|
||||
Selected: {n}, Total number of files to export: {m} 選択済み:{n}、書き出すファイルの合計数:{m}
|
||||
Create Text テキストを作成
|
||||
Created 作成日
|
||||
Created: 作成日:
|
||||
@@ -4425,6 +4432,7 @@ swatch Bright Violet ビビッドバイオレット
|
||||
@msg nothing to export: the document has no visible art (or turn on Use Artboards) 書き出すものがありません: ドキュメントに表示されているアートがありません (または「アートボードを使用」をオンにしてください)
|
||||
@msg add a format to export 書き出す形式を追加してください
|
||||
@msg check an asset to export 書き出すアセットにチェックを入れてください
|
||||
@msg check an artboard to export 書き出すアートボードにチェックを入れてください
|
||||
@msg check a layer to export 書き出すレイヤーにチェックを入れてください
|
||||
@msg select the art to export first 先に書き出すアートを選択してください
|
||||
@msg `{tool}` isn't a freehand tool `{tool}` はフリーハンドツールではありません
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1961,6 +1961,13 @@ lighting Light სინათლე
|
||||
Use Artboards სამუშაო დაფების გამოყენება
|
||||
Choose a folder საქაღალდის არჩევა
|
||||
Create Sub-folders ქვესაქაღალდეების შექმნა
|
||||
Clear Selection მონიშვნის გასუფთავება
|
||||
Export PDFs As: PDF-ების ექსპორტი როგორც:
|
||||
Single File ერთი ფაილი
|
||||
Multiple Files რამდენიმე ფაილი
|
||||
Format Presets ფორმატის პრესეტები
|
||||
Grid View ბადის ხედი
|
||||
Selected: {n}, Total number of files to export: {m} არჩეულია: {n}, ექსპორტის ფაილების საერთო რაოდენობა: {m}
|
||||
Export Asset რესურსის ექსპორტირება
|
||||
Export to: ექსპორტის ადგილი:
|
||||
Format Settings ფორმატის პარამეტრები
|
||||
@@ -3851,6 +3858,7 @@ font-technology Variable ცვლადი
|
||||
@msg cannot split an open path at its end point ღია ტრაექტორიის ბოლო წერტილში გაყოფა შეუძლებელია
|
||||
@msg closing needs three anchors შეკვრას სამი საყრდენი წერტილი სჭირდება
|
||||
@msg check an asset to export მონიშნეთ ექსპორტის რესურსი
|
||||
@msg check an artboard to export მონიშნეთ ექსპორტის სახატავი დაფა
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice უნდა იყოს "merge" ან "add" და არა `{choice}`
|
||||
@msg choose a direction to divide in აირჩიეთ დაყოფის მიმართულება
|
||||
@msg choose a folder to export to აირჩიეთ ექსპორტის საქაღალდე
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1222,6 +1222,13 @@ font-technology Variable 가변
|
||||
Create Path 패스 만들기
|
||||
Create Slice 슬라이스 만들기
|
||||
Create Sub-folders 하위 폴더 만들기
|
||||
Clear Selection 선택 지우기
|
||||
Export PDFs As: PDF 내보내기 방식:
|
||||
Single File 단일 파일
|
||||
Multiple Files 여러 파일
|
||||
Format Presets 형식 사전 설정
|
||||
Grid View 격자 보기
|
||||
Selected: {n}, Total number of files to export: {m} 선택됨: {n}, 내보낼 총 파일 수: {m}
|
||||
Create Text 텍스트 만들기
|
||||
Created 만든 날짜
|
||||
Created: 만든 날짜:
|
||||
@@ -4353,6 +4360,7 @@ theme Appearance 외관
|
||||
@msg nothing to export: the document has no visible art (or turn on Use Artboards) 내보낼 항목이 없습니다: 문서에 보이는 아트웍이 없습니다(또는 아트보드 사용을 켜세요)
|
||||
@msg add a format to export 내보낼 형식을 추가하세요
|
||||
@msg check an asset to export 내보낼 자산을 체크하세요
|
||||
@msg check an artboard to export 내보낼 대지를 체크하세요
|
||||
@msg check a layer to export 내보낼 레이어를 선택하십시오
|
||||
@msg select the art to export first 먼저 내보낼 아트를 선택하십시오
|
||||
@msg `{tool}` isn't a freehand tool `{tool}`은(는) 프리핸드 도구가 아닙니다
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2075,6 +2075,13 @@ lighting Light Licht
|
||||
Use Artboards Tekenborden gebruiken
|
||||
Choose a folder Kies een map
|
||||
Create Sub-folders Submappen maken
|
||||
Clear Selection Selectie wissen
|
||||
Export PDFs As: PDF's exporteren als:
|
||||
Single File Eén bestand
|
||||
Multiple Files Meerdere bestanden
|
||||
Format Presets Indelingsvoorinstellingen
|
||||
Grid View Rasterweergave
|
||||
Selected: {n}, Total number of files to export: {m} Geselecteerd: {n}, totaal aantal te exporteren bestanden: {m}
|
||||
Export Asset Asset exporteren
|
||||
Export to: Exporteren naar:
|
||||
Format Settings Indelingsinstellingen
|
||||
@@ -4081,6 +4088,7 @@ imageTrace Outline Contour
|
||||
@msg cannot split an open path at its end point kan een open pad niet splitsen op zijn eindpunt
|
||||
@msg closing needs three anchors voor sluiten zijn drie ankers nodig
|
||||
@msg check an asset to export vink een asset aan om te exporteren
|
||||
@msg check an artboard to export vink een tekengebied aan om te exporteren
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice moet "merge" of "add" zijn, niet `{choice}`
|
||||
@msg choose a direction to divide in kies een richting om in te verdelen
|
||||
@msg choose a folder to export to kies een map om naar te exporteren
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2174,6 +2174,13 @@ lighting Light Światło
|
||||
Use Artboards Użyj obszarów kompozycji
|
||||
Choose a folder Wybierz folder
|
||||
Create Sub-folders Utwórz podfoldery
|
||||
Clear Selection Wyczyść zaznaczenie
|
||||
Export PDFs As: Eksportuj pliki PDF jako:
|
||||
Single File Jeden plik
|
||||
Multiple Files Wiele plików
|
||||
Format Presets Ustawienia formatów
|
||||
Grid View Widok siatki
|
||||
Selected: {n}, Total number of files to export: {m} Zaznaczono: {n}, łączna liczba plików do eksportu: {m}
|
||||
Export Asset Eksportuj zasób
|
||||
Export to: Eksportuj do:
|
||||
Format Settings Ustawienia formatu
|
||||
@@ -4199,6 +4206,7 @@ imageTrace Outline Kontur
|
||||
@msg cannot split an open path at its end point nie można podzielić otwartej ścieżki w jej punkcie końcowym
|
||||
@msg closing needs three anchors zamknięcie wymaga trzech punktów
|
||||
@msg check an asset to export zaznacz zasób do eksportu
|
||||
@msg check an artboard to export zaznacz obszar kompozycji do eksportu
|
||||
@msg check a layer to export zaznacz warstwę do eksportu
|
||||
@msg select the art to export first najpierw zaznacz grafikę do eksportu
|
||||
@msg choice must be "merge" or "add", not `{choice}` wybór musi mieć wartość "merge" lub "add", nie `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -585,6 +585,13 @@
|
||||
Create Path Criar Caminho
|
||||
Create Slice Criar Fatia
|
||||
Create Sub-folders Criar Subpastas
|
||||
Clear Selection Limpar Seleção
|
||||
Export PDFs As: Exportar PDFs Como:
|
||||
Single File Arquivo Único
|
||||
Multiple Files Vários Arquivos
|
||||
Format Presets Predefinições de Formato
|
||||
Grid View Exibição em Grade
|
||||
Selected: {n}, Total number of files to export: {m} Selecionados: {n}, número total de arquivos a exportar: {m}
|
||||
Create Text Criar Texto
|
||||
Create Trim Marks Criar Marcas de Corte
|
||||
Create from Guides Criar a partir de Guias
|
||||
@@ -3646,6 +3653,7 @@ axis Both Ambos
|
||||
@msg cannot split an open path at its end point não é possível dividir um traçado aberto em sua extremidade
|
||||
@msg closing needs three anchors fechar exige três âncoras
|
||||
@msg check an asset to export marque um recurso para exportar
|
||||
@msg check an artboard to export marque uma prancheta para exportar
|
||||
@msg check a layer to export marque uma camada para exportar
|
||||
@msg select the art to export first selecione primeiro a arte a exportar
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice deve ser "merge" ou "add", não `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -661,6 +661,13 @@ font-technology Variable Вариативные
|
||||
Create Report Создать отчёт
|
||||
Create Slice Создать фрагмент
|
||||
Create Sub-folders Создать вложенные папки
|
||||
Clear Selection Снять выделение
|
||||
Export PDFs As: Экспортировать PDF как:
|
||||
Single File Один файл
|
||||
Multiple Files Несколько файлов
|
||||
Format Presets Наборы форматов
|
||||
Grid View Сетка
|
||||
Selected: {n}, Total number of files to export: {m} Выбрано: {n}, всего файлов для экспорта: {m}
|
||||
Create Text Создать текст
|
||||
Create Trim Marks Создать метки подрезки
|
||||
Created Создано
|
||||
@@ -4104,6 +4111,7 @@ axis Both Оба
|
||||
@msg cannot split an open path at its end point нельзя разделить открытый контур в его конечной точке
|
||||
@msg closing needs three anchors для замыкания нужны три опорные точки
|
||||
@msg check an asset to export отметьте ресурс для экспорта
|
||||
@msg check an artboard to export отметьте монтажную область для экспорта
|
||||
@msg check a layer to export отметьте слой для экспорта
|
||||
@msg select the art to export first сначала выделите объекты для экспорта
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice должно быть "merge" или "add", а не `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -651,6 +651,13 @@ font-technology Variable Variabla
|
||||
Create Report Skapa rapport
|
||||
Create Slice Skapa skiva
|
||||
Create Sub-folders Skapa undermappar
|
||||
Clear Selection Rensa markering
|
||||
Export PDFs As: Exportera PDF-filer som:
|
||||
Single File En fil
|
||||
Multiple Files Flera filer
|
||||
Format Presets Formatförinställningar
|
||||
Grid View Rutnätsvy
|
||||
Selected: {n}, Total number of files to export: {m} Markerade: {n}, totalt antal filer att exportera: {m}
|
||||
Create Text Skapa text
|
||||
Create Trim Marks Skapa skärmärken
|
||||
Created Skapad
|
||||
@@ -3979,6 +3986,7 @@ axis Both Båda
|
||||
@msg cannot split an open path at its end point kan inte dela en öppen bana vid dess slutpunkt
|
||||
@msg closing needs three anchors att sluta banan kräver tre ankarpunkter
|
||||
@msg check an asset to export kryssa för en resurs att exportera
|
||||
@msg check an artboard to export kryssa för en rityta att exportera
|
||||
@msg check a layer to export markera ett lager att exportera
|
||||
@msg select the art to export first markera först teckningen som ska exporteras
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice måste vara "merge" eller "add", inte `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -643,6 +643,13 @@ font-technology Variable Değişken
|
||||
Create Report Rapor Oluştur
|
||||
Create Slice Dilim Oluştur
|
||||
Create Sub-folders Alt Klasörler Oluşturun
|
||||
Clear Selection Seçimi Temizle
|
||||
Export PDFs As: PDF'leri Şu Şekilde Dışa Aktar:
|
||||
Single File Tek Dosya
|
||||
Multiple Files Birden Fazla Dosya
|
||||
Format Presets Biçim Ön Ayarları
|
||||
Grid View Izgara Görünümü
|
||||
Selected: {n}, Total number of files to export: {m} Seçilen: {n}, dışa aktarılacak toplam dosya sayısı: {m}
|
||||
Create Text Metin Oluştur
|
||||
Create Trim Marks Kesim İşaretleri Oluşturun
|
||||
Created Oluşturuldu
|
||||
@@ -3848,6 +3855,7 @@ axis Both Her İkisi
|
||||
@msg cannot split an open path at its end point açık bir yolu bitiş noktasında bölemez
|
||||
@msg closing needs three anchors Kapanış üç çapaya ihtiyaç duyar
|
||||
@msg check an asset to export dışa aktarılacak bir varlığı kontrol etme
|
||||
@msg check an artboard to export dışa aktarılacak bir çalışma yüzeyi işaretleyin
|
||||
@msg check a layer to export dışa aktarılacak katmanı kontrol edin
|
||||
@msg select the art to export first önce dışa aktarılacak resmi seçin
|
||||
@msg choice must be "merge" or "add", not `{choice}` seçim "{choice}" değil "birleştir" veya "ekle" olmalıdır
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -657,6 +657,13 @@ font-technology Variable Варіативні
|
||||
Create Report Створити звіт
|
||||
Create Slice Створити фрагмент
|
||||
Create Sub-folders Створити вкладені папки
|
||||
Clear Selection Зняти виділення
|
||||
Export PDFs As: Експортувати PDF як:
|
||||
Single File Один файл
|
||||
Multiple Files Кілька файлів
|
||||
Format Presets Набори форматів
|
||||
Grid View Сітка
|
||||
Selected: {n}, Total number of files to export: {m} Вибрано: {n}, усього файлів для експорту: {m}
|
||||
Create Text Створити текст
|
||||
Create Trim Marks Створити мітки підрізування
|
||||
Created Створено
|
||||
@@ -4092,6 +4099,7 @@ axis Both Обидві
|
||||
@msg cannot split an open path at its end point не можна розділити відкритий контур у його кінцевій точці
|
||||
@msg closing needs three anchors для замикання потрібні три опорні точки
|
||||
@msg check an asset to export позначте ресурс для експорту
|
||||
@msg check an artboard to export позначте монтажну область для експорту
|
||||
@msg check a layer to export позначте шар для експорту
|
||||
@msg select the art to export first спочатку виділіть об’єкти для експорту
|
||||
@msg choice must be "merge" or "add", not `{choice}` choice має бути "merge" або "add", а не `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 178 and column 5.
|
@@ -760,6 +760,13 @@ font-technology Variable Biến thiên
|
||||
Create Report Tạo báo cáo
|
||||
Create Slice Tạo lát cắt
|
||||
Create Sub-folders Tạo thư mục con
|
||||
Clear Selection Xóa lựa chọn
|
||||
Export PDFs As: Xuất PDF dưới dạng:
|
||||
Single File Một tệp
|
||||
Multiple Files Nhiều tệp
|
||||
Format Presets Thiết lập sẵn định dạng
|
||||
Grid View Chế độ lưới
|
||||
Selected: {n}, Total number of files to export: {m} Đã chọn: {n}, tổng số tệp sẽ xuất: {m}
|
||||
Create Text Tạo văn bản
|
||||
Create Trim Marks Tạo dấu cắt xén
|
||||
Create: Tạo:
|
||||
@@ -4274,6 +4281,7 @@ theme Appearance Giao diện
|
||||
@msg cannot read PDF: {0} không thể đọc PDF: {0}
|
||||
@msg cannot split an open path at its end point không thể tách đường dẫn mở tại điểm cuối
|
||||
@msg check an asset to export hãy chọn một asset để xuất
|
||||
@msg check an artboard to export hãy chọn một bảng vẽ để xuất
|
||||
@msg check a layer to export hãy đánh dấu một lớp để xuất
|
||||
@msg select the art to export first hãy chọn hình cần xuất trước
|
||||
@msg choice must be "merge" or "add", not `{choice}` lựa chọn phải là "merge" hoặc "add", không phải `{choice}`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 200 and column 5.
|
@@ -608,6 +608,13 @@
|
||||
Create Report 创建报告
|
||||
Create Slice 创建切片
|
||||
Create Sub-folders 创建子文件夹
|
||||
Clear Selection 清除选择
|
||||
Export PDFs As: PDF 导出为:
|
||||
Single File 单个文件
|
||||
Multiple Files 多个文件
|
||||
Format Presets 格式预设
|
||||
Grid View 网格视图
|
||||
Selected: {n}, Total number of files to export: {m} 已选择:{n},要导出的文件总数:{m}
|
||||
Create Text 创建文字
|
||||
Create Trim Marks 创建裁切标记
|
||||
Created 创建时间
|
||||
@@ -4148,6 +4155,7 @@ imageTrace Outline 轮廓
|
||||
@msg nothing to export: the document has no visible art (or turn on Use Artboards) 没有可导出的内容:文档中没有可见的图稿 (或请打开“使用画板”)
|
||||
@msg add a format to export 请添加要导出的格式
|
||||
@msg check an asset to export 请勾选要导出的资源
|
||||
@msg check an artboard to export 请勾选要导出的画板
|
||||
@msg `{tool}` isn't a freehand tool `{tool}` 不是手绘工具
|
||||
@msg bad colour `{hex}` (#rrggbb) 颜色 `{hex}` 无效 (#rrggbb)
|
||||
@msg no gradient stop or freeform point is selected 未选择渐变色标或任意形状点
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -625,6 +625,13 @@ font-technology Variable 可變
|
||||
Create Report 建立報告
|
||||
Create Slice 建立切片
|
||||
Create Sub-folders 建立子資料夾
|
||||
Clear Selection 清除選取
|
||||
Export PDFs As: PDF 轉存為:
|
||||
Single File 單一檔案
|
||||
Multiple Files 多個檔案
|
||||
Format Presets 格式預設集
|
||||
Grid View 格點檢視
|
||||
Selected: {n}, Total number of files to export: {m} 已選取:{n},要轉存的檔案總數:{m}
|
||||
Create Text 建立文字
|
||||
Create Trim Marks 建立剪裁標記
|
||||
Created 建立時間
|
||||
@@ -2088,6 +2095,7 @@ file-filter Templates 範本
|
||||
@msg nothing to export: the document has no visible art (or turn on Use Artboards) 沒有可匯出的內容:文件中沒有可見的圖稿 (或請開啟「使用工作區域」)
|
||||
@msg add a format to export 請新增要匯出的格式
|
||||
@msg check an asset to export 請勾選要匯出的資產
|
||||
@msg check an artboard to export 請勾選要匯出的工作區域
|
||||
@msg `{tool}` isn't a freehand tool `{tool}` 不是手繪工具
|
||||
@msg bad colour `{hex}` (#rrggbb) 顏色 `{hex}` 無效 (#rrggbb)
|
||||
@msg no gradient stop or freeform point is selected 未選取漸層色標或任意形狀點
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -285,6 +285,8 @@ fn report_exported(app: &mut VectorcraftApp, r: Value, folder: &str, open_locati
|
||||
}
|
||||
app.status(status);
|
||||
} else if let Some(download) = app.services.download.as_mut() {
|
||||
// A browser download has no folders: a file from a sub-folder (`2x/Logo@2x.png`) is named alone.
|
||||
let returned: Vec<(&str, &str)> = returned.iter().map(|(name, data)| (name.rsplit('/').next().unwrap_or(name), *data)).collect();
|
||||
for (name, data) in &returned {
|
||||
let bytes = vectorcraft_format::base64_decode(data).ok_or("the export returned unreadable data")?;
|
||||
download(name, &bytes)?;
|
||||
|
||||
@@ -263,7 +263,7 @@ fn settings(ui: &mut Ui, st: &DocState) -> (Option<Settings>, usize) {
|
||||
let preset = preset.unwrap_or_default().to_string();
|
||||
let n = rows.len();
|
||||
let mut d = Dialog::new(EXPORT_FOR_SCREENS, json!({ "formats": &rows, "preset": preset, "__settings": "" }));
|
||||
screen_formats(ui, &mut d);
|
||||
screen_formats(ui, &mut d, None);
|
||||
let (shown, picked) = (d.str("__settings"), d.str("preset"));
|
||||
let formats = d.fields.remove("formats").unwrap_or(Value::Null);
|
||||
let change = if !shown.is_empty() {
|
||||
|
||||
@@ -209,7 +209,7 @@ fn the_rows_are_the_shared_export_settings() {
|
||||
// A preset set by an agent shows as its rows.
|
||||
app.run("assets.settings.set", json!({"preset": "mobile"})).unwrap();
|
||||
let texts = frame(&ctx, &mut app, vec![], false);
|
||||
assert!(has(&texts, "Mobile 1x/2x/3x") && has(&texts, "@3x"));
|
||||
assert!(has(&texts, "iOS") && has(&texts, "@3x"));
|
||||
// Export (nothing selected: every asset) downloads one zip on the web.
|
||||
let texts = frame(&ctx, &mut app, vec![], false);
|
||||
frame(&ctx, &mut app, click(at(&texts, "Export")), false);
|
||||
|
||||
Reference in New Issue
Block a user