mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-12 08:52:26 +00:00
Merge pull request #6399 from dotCooCoo/feat/api-reference
Commands: engine.apiReference returns the command API reference in Markdown
This commit is contained in:
@@ -597,7 +597,7 @@ fn exercise_mcp_core(
|
||||
assert!(r["result"]["completion"]["values"].as_array().is_some_and(|v| v.contains(&json!("svg"))), "{r}");
|
||||
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":5,"method":"resources/list","params":{}}));
|
||||
assert!(mcp_recv(lines, notes, 5)["result"]["resources"].as_array().is_some_and(|v| v.len() == 3));
|
||||
assert!(mcp_recv(lines, notes, 5)["result"]["resources"].as_array().is_some_and(|v| v.len() == 4));
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":6,"method":"resources/templates/list","params":{}}));
|
||||
assert!(mcp_recv(lines, notes, 6)["result"]["resourceTemplates"].as_array().is_some_and(|v| v.len() >= 4));
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
pub mod actionset;
|
||||
mod addons;
|
||||
mod apiref;
|
||||
pub(crate) mod appearance;
|
||||
mod assets;
|
||||
mod attributes;
|
||||
@@ -384,6 +385,8 @@ pub fn command_specs() -> &'static [CommandSpec] {
|
||||
v.extend(actionset::specs());
|
||||
v.extend(importmatches::specs());
|
||||
v.extend(addons::specs());
|
||||
// Last, so the reference it writes ends with its own section.
|
||||
v.extend(apiref::specs());
|
||||
v
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,461 @@
|
||||
//! `engine.apiReference`: the command API reference in Markdown, written when it is asked for from
|
||||
//! the parameter schemas of the built-in commands ([`crate::schemas`]). Each command that has a
|
||||
//! schema gets a section, in registry order, with its parameters in the schema's order; a command
|
||||
//! without a schema, a plug-in's included, is left out. The text depends only on the registry, so
|
||||
//! every call in one build returns the same bytes.
|
||||
//!
|
||||
//! Text from a schema is escaped before it goes into a heading, a list item or a table cell: the
|
||||
//! Markdown punctuation characters get a backslash (CommonMark 0.31.2, backslash escapes,
|
||||
//! <https://spec.commonmark.org/0.31.2/#backslash-escapes>), so a description shows as plain text and
|
||||
//! cannot end a table cell or make a link, an image or HTML; line breaks, tabs and other control
|
||||
//! characters become spaces.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_apischema::{Bound, MAX_DEPTH, MAX_ID, Omitted, Param, ParamSchema, Type, Writes};
|
||||
|
||||
use super::{CommandSpec, Result, Session, always, cmd, command_specs};
|
||||
use crate::EngineError;
|
||||
|
||||
const ID: &str = "engine.apiReference";
|
||||
|
||||
/// The largest reference the command returns, in bytes of UTF-8.
|
||||
pub const MAX_BYTES: usize = 1 << 20;
|
||||
|
||||
static REFERENCE: ParamSchema = ParamSchema::new(
|
||||
&[],
|
||||
"the command API reference in Markdown, written from the built-in commands' parameter schemas: a section for each command that has one, in registry order",
|
||||
)
|
||||
.returns("{markdown: string}")
|
||||
.writes(Writes::Nothing)
|
||||
.since(2);
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![cmd!(
|
||||
query "engine.apiReference",
|
||||
"Command API Reference",
|
||||
[],
|
||||
None,
|
||||
"{} the command API reference in Markdown, written from the built-in commands' parameter schemas: a section for each command that has one, in registry order → {markdown: string}",
|
||||
always,
|
||||
api_reference,
|
||||
schema: &REFERENCE
|
||||
)]
|
||||
}
|
||||
|
||||
fn api_reference(_: &mut Session, _: &Value) -> Result<Value> {
|
||||
Ok(json!({ "markdown": reference(command_specs())? }))
|
||||
}
|
||||
|
||||
const HEADER: &str = "# VectorCraft command API reference\n\nThis reference lists only the commands that have a parameter schema, in registry order. `engine.commands` lists every command, with its params text and whether it can run now.\n";
|
||||
|
||||
/// The reference for the commands of `specs` that have a schema, or an error when it would be
|
||||
/// larger than [`MAX_BYTES`]; a reference that is too large is never cut short.
|
||||
pub(crate) fn reference(specs: &[CommandSpec]) -> Result<String> {
|
||||
let mut out = String::from(HEADER);
|
||||
let mut sections = 0;
|
||||
for spec in specs {
|
||||
let Some(schema) = spec.schema else { continue };
|
||||
let text = section(spec, schema);
|
||||
if out.len() + text.len() > MAX_BYTES {
|
||||
return Err(EngineError::Other(format!("`{ID}`: the reference would be larger than {MAX_BYTES} bytes")));
|
||||
}
|
||||
out.push_str(&text);
|
||||
sections += 1;
|
||||
}
|
||||
if sections == 0 {
|
||||
out.push_str("\nNo commands have parameter schemas.\n");
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// One command's section: its heading, a list of what it is, and its parameters.
|
||||
fn section(spec: &CommandSpec, schema: &ParamSchema) -> String {
|
||||
let menu = if spec.menu.is_empty() { "None".to_string() } else { spec.menu.iter().map(|m| escape(m)).collect::<Vec<_>>().join(" › ") };
|
||||
let mut s = format!("\n## {}\n\n", escape(spec.id));
|
||||
s.push_str(&format!("- Label: {}\n", escape(spec.label)));
|
||||
s.push_str(&format!("- Menu: {menu}\n"));
|
||||
s.push_str(&format!("- API level: {}\n", schema.since));
|
||||
s.push_str(&format!("- Deprecated: {}\n", level_text(schema.deprecated)));
|
||||
s.push_str(&format!("- Writes: {}\n", writes_text(schema.writes)));
|
||||
s.push_str(&format!("- Cost: {}\n", schema.cost));
|
||||
s.push_str(&format!("- Summary: {}\n", text_or_none(schema.summary)));
|
||||
s.push_str(&format!("- Returns: {}\n\n", text_or_none(schema.returns)));
|
||||
if schema.params.is_empty() {
|
||||
s.push_str("No parameters.\n");
|
||||
return s;
|
||||
}
|
||||
s.push_str("| Parameter | Type | Range | Unit | Default | Required | Since | Deprecated | Description |\n");
|
||||
s.push_str("|---|---|---|---|---|---|---|---|---|\n");
|
||||
rows("", schema.params, 0, &mut s);
|
||||
s
|
||||
}
|
||||
|
||||
/// A table row for each of `params`, and after each one the rows of the members of an object it
|
||||
/// holds: `edits[].name` for an object in a list, `options.size` for an object.
|
||||
fn rows(prefix: &str, params: &[Param], depth: usize, out: &mut String) {
|
||||
for p in params {
|
||||
let name = if prefix.is_empty() { p.name.to_string() } else { format!("{prefix}.{}", p.name) };
|
||||
let (range, unit) = range_and_unit(&p.ty);
|
||||
out.push_str(&format!(
|
||||
"| {} | {} | {range} | {unit} | {} | {} | {} | {} | {} |\n",
|
||||
escape(&name),
|
||||
type_text(&p.ty, depth),
|
||||
default_text(p.omitted),
|
||||
if p.required { "Yes" } else { "No" },
|
||||
p.since,
|
||||
level_text(p.deprecated),
|
||||
description(p),
|
||||
));
|
||||
if depth < MAX_DEPTH
|
||||
&& let Some((members, path)) = members(&p.ty, name, depth)
|
||||
{
|
||||
rows(&path, members, depth + 1, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters of the object `ty` holds, directly or as the items of lists, with the path of
|
||||
/// its members' prefix (`name`, `name[]`, `name[][]`).
|
||||
fn members(ty: &Type, mut path: String, depth: usize) -> Option<(&'static [Param], String)> {
|
||||
let mut ty = ty;
|
||||
for _ in depth..MAX_DEPTH {
|
||||
match ty {
|
||||
Type::Object(params) => return Some((params, path)),
|
||||
Type::Array(item) => {
|
||||
path.push_str("[]");
|
||||
ty = item;
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn type_text(ty: &Type, depth: usize) -> String {
|
||||
match ty {
|
||||
Type::Number { .. } => "number".into(),
|
||||
Type::Integer { .. } => "integer".into(),
|
||||
Type::Boolean => "boolean".into(),
|
||||
Type::String => "string".into(),
|
||||
Type::Enum(names) => format!("one of {}", names.iter().map(|n| format!("\"{}\"", escape(n))).collect::<Vec<_>>().join(", ")),
|
||||
Type::Id => "object id (integer)".into(),
|
||||
Type::Ids => "list of object ids".into(),
|
||||
Type::Color => escape("color (a string, [r, g, b] or an object)"),
|
||||
Type::Point => escape("point ([x, y], two numbers)"),
|
||||
Type::Object(_) => "object".into(),
|
||||
Type::Array(_) if depth >= MAX_DEPTH => "list".into(),
|
||||
Type::Array(item) => format!("list of {}", type_text(item, depth + 1)),
|
||||
}
|
||||
}
|
||||
|
||||
/// The range and unit cells of a value of type `ty`.
|
||||
fn range_and_unit(ty: &Type) -> (String, String) {
|
||||
let ends = |low: Option<String>, high: Option<String>| {
|
||||
let both: Vec<String> = low.into_iter().chain(high).collect();
|
||||
if both.is_empty() { "None".to_string() } else { both.join(", ") }
|
||||
};
|
||||
match ty {
|
||||
Type::Number { min, max, unit } => {
|
||||
let low = min.map(|b| match b {
|
||||
Bound::Inclusive(a) => format!("at least {a}"),
|
||||
Bound::Exclusive(a) => format!("more than {a}"),
|
||||
});
|
||||
let high = max.map(|b| match b {
|
||||
Bound::Inclusive(a) => format!("at most {a}"),
|
||||
Bound::Exclusive(a) => format!("less than {a}"),
|
||||
});
|
||||
(ends(low, high), unit.map_or("None".to_string(), escape))
|
||||
}
|
||||
Type::Integer { min, max } => (ends(min.map(|a| format!("at least {a}")), max.map(|a| format!("at most {a}"))), "None".into()),
|
||||
Type::Id => (format!("at least 0, at most {MAX_ID}"), "None".into()),
|
||||
_ => ("None".into(), "None".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn default_text(omitted: Omitted) -> String {
|
||||
match omitted {
|
||||
Omitted::Nothing => "None".into(),
|
||||
Omitted::Value(json) => format!("{} (JSON)", escape(json)),
|
||||
Omitted::Note(note) => format!("{} (worked out by the command)", escape(note)),
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameter's doc text, and what the schema says about how its value is checked.
|
||||
fn description(p: &Param) -> String {
|
||||
let mut parts = Vec::new();
|
||||
if !p.doc.is_empty() {
|
||||
parts.push(escape(p.doc));
|
||||
}
|
||||
if p.null_omitted {
|
||||
parts.push("null reads as omitted".to_string());
|
||||
}
|
||||
if p.lenient {
|
||||
parts.push("lenient (a value of another type or out of range is passed to the command, not refused)".to_string());
|
||||
}
|
||||
if parts.is_empty() { "None".to_string() } else { parts.join("; ") }
|
||||
}
|
||||
|
||||
fn level_text(level: Option<u32>) -> String {
|
||||
level.map_or("None".to_string(), |l| format!("API level {l}"))
|
||||
}
|
||||
|
||||
fn writes_text(writes: Writes) -> &'static str {
|
||||
match writes {
|
||||
Writes::Nothing => "nothing",
|
||||
Writes::Session => "session",
|
||||
Writes::Preferences => "preferences",
|
||||
Writes::Document => "document",
|
||||
}
|
||||
}
|
||||
|
||||
fn text_or_none(text: &str) -> String {
|
||||
if text.is_empty() { "None".to_string() } else { escape(text) }
|
||||
}
|
||||
|
||||
/// The characters [`escape`] puts a backslash before.
|
||||
const ESCAPED: &[char] = &['\\', '|', '[', ']', '<', '>', '`', '*', '_', '!', '&', '#', '~', ':', '@'];
|
||||
|
||||
/// `text` as plain Markdown text on one line: each of [`ESCAPED`] with a backslash before it, the
|
||||
/// `.` after `www` too (so no autolink starts), and each control character as a space.
|
||||
pub(crate) fn escape(text: &str) -> String {
|
||||
let mut out = String::with_capacity(text.len());
|
||||
for c in text.chars() {
|
||||
if c.is_control() {
|
||||
out.push(' ');
|
||||
continue;
|
||||
}
|
||||
let after_www = out.len().checked_sub(3).and_then(|at| out.as_bytes().get(at..)).is_some_and(|b| b.eq_ignore_ascii_case(b"www"));
|
||||
if ESCAPED.contains(&c) || (c == '.' && after_www) {
|
||||
out.push('\\');
|
||||
}
|
||||
out.push(c);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn run(_: &mut Session, _: &Value) -> Result<Value> {
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
fn spec(id: &'static str, menu: &'static [&'static str], schema: Option<&'static ParamSchema>) -> CommandSpec {
|
||||
CommandSpec { id, label: "Label", menu, shortcut: None, params: "", enabled: always, run, journal: false, schema }
|
||||
}
|
||||
|
||||
/// The cells of a table row as a GFM table reads them: split at each `|` that has no backslash
|
||||
/// before it, with the outer pipes and the spaces around each cell taken off.
|
||||
fn cells(row: &str) -> Vec<String> {
|
||||
let inner = row.strip_prefix('|').and_then(|r| r.strip_suffix('|')).unwrap_or_else(|| panic!("not a table row: {row}"));
|
||||
let (mut out, mut cell, mut chars) = (Vec::new(), String::new(), inner.chars());
|
||||
while let Some(c) = chars.next() {
|
||||
match c {
|
||||
'\\' => {
|
||||
cell.push(c);
|
||||
cell.extend(chars.next());
|
||||
}
|
||||
'|' => out.push(std::mem::take(&mut cell).trim().to_string()),
|
||||
_ => cell.push(c),
|
||||
}
|
||||
}
|
||||
out.push(cell.trim().to_string());
|
||||
out
|
||||
}
|
||||
|
||||
/// What `text` shows as: each escaped character as itself. Panics on a Markdown punctuation
|
||||
/// character without its backslash, or a backslash before a character that needs none.
|
||||
fn literal(text: &str) -> String {
|
||||
let (mut out, mut chars) = (String::new(), text.chars());
|
||||
while let Some(c) = chars.next() {
|
||||
assert!(!ESCAPED.contains(&c) || c == '\\', "`{c}` is not escaped in {text}");
|
||||
if c == '\\' {
|
||||
let next = chars.next().unwrap_or_else(|| panic!("a backslash ends {text}"));
|
||||
assert!(ESCAPED.contains(&next) || next == '.', "`\\{next}` in {text}");
|
||||
out.push(next);
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The shown cells of the row of parameter `name`.
|
||||
fn row(markdown: &str, name: &str) -> Vec<String> {
|
||||
let found = markdown.lines().filter(|l| l.starts_with('|')).find(|l| cells(l).first().map(|c| literal(c)).as_deref() == Some(name));
|
||||
let row = found.unwrap_or_else(|| panic!("no row for {name} in\n{markdown}"));
|
||||
let cells = cells(row);
|
||||
assert_eq!(cells.len(), 9, "{row}");
|
||||
cells.iter().map(|c| literal(c)).collect()
|
||||
}
|
||||
|
||||
/// The text after `- {label}: ` in the section's list.
|
||||
fn item(markdown: &str, label: &str) -> String {
|
||||
let prefix = format!("- {label}: ");
|
||||
let line = markdown.lines().find_map(|l| l.strip_prefix(prefix.as_str()));
|
||||
literal(line.unwrap_or_else(|| panic!("no {label} in\n{markdown}")))
|
||||
}
|
||||
|
||||
/// Two calls give the same bytes, with no document open, and the reference has one section
|
||||
/// for each built-in command that has a schema, in registry order, ending with this command's.
|
||||
#[test]
|
||||
fn the_reference_lists_each_schema_once_in_registry_order_and_is_the_same_each_time() {
|
||||
let mut s = Session::new();
|
||||
let first = s.execute(ID, &json!({})).unwrap();
|
||||
let second = s.execute(ID, &Value::Null).unwrap();
|
||||
assert_eq!(first, second);
|
||||
let markdown = first["markdown"].as_str().unwrap();
|
||||
assert_eq!(markdown, reference(command_specs()).unwrap());
|
||||
assert!(markdown.len() < MAX_BYTES / 10, "{} bytes", markdown.len());
|
||||
let headings: Vec<String> = markdown.lines().filter_map(|l| l.strip_prefix("## ")).map(literal).collect();
|
||||
let expected: Vec<&str> = command_specs().iter().filter(|c| c.schema.is_some()).map(|c| c.id).collect();
|
||||
assert_eq!(headings, expected);
|
||||
assert_eq!(headings.last().map(String::as_str), Some(ID));
|
||||
assert!(expected.contains(&"view.saved.new") && !headings.iter().any(|h| h == "file.new"));
|
||||
let new_view = &markdown[markdown.find("## view.saved.new").unwrap()..];
|
||||
let names: Vec<String> = new_view
|
||||
.lines()
|
||||
.skip_while(|l| !l.starts_with("|---"))
|
||||
.skip(1)
|
||||
.take_while(|l| l.starts_with('|'))
|
||||
.map(|l| literal(&cells(l)[0]))
|
||||
.collect();
|
||||
assert_eq!(names, ["name", "center", "zoom", "rotation"]);
|
||||
let edit = &markdown[markdown.find("## view.saved.edit").unwrap()..];
|
||||
assert_eq!(row(edit, "edits[].newName")[8], "null reads as omitted");
|
||||
assert_eq!(row(edit, "newName")[8], "rename; null reads as omitted");
|
||||
}
|
||||
|
||||
const INNER: &[Param] =
|
||||
&[Param::new("size", Type::Integer { min: Some(1), max: Some(10) }), Param::new("mode", Type::Enum(&["a", "b"])).defaults_to("\"a\"")];
|
||||
|
||||
static FULL: ParamSchema = ParamSchema::new(
|
||||
&[
|
||||
Param::new("zoom", Type::Number { min: Some(Bound::Exclusive(0.0)), max: Some(Bound::Inclusive(64.0)), unit: Some("pt") })
|
||||
.doc("1 = 100%"),
|
||||
Param::new("name", Type::String).default_note("the first free name").null_omitted(),
|
||||
Param::new("old", Type::Boolean).defaults_to("true").since(2).deprecated(3).lenient(),
|
||||
Param::new("edits", Type::Array(&Type::Object(INNER))).optional(),
|
||||
Param::new("ids", Type::Ids).optional(),
|
||||
Param::new("at", Type::Point).optional(),
|
||||
Param::new("color", Type::Color).optional(),
|
||||
Param::new("id", Type::Id).optional(),
|
||||
Param::new("ratio", Type::Number { min: Some(Bound::Inclusive(-1.5)), max: Some(Bound::Exclusive(2.0)), unit: None }).optional(),
|
||||
],
|
||||
"do the thing",
|
||||
)
|
||||
.returns("{done}")
|
||||
.writes(Writes::Preferences)
|
||||
.since(2)
|
||||
.deprecated(4)
|
||||
.cost(5);
|
||||
|
||||
/// Each field of a schema shows, nested members in their order under dotted names.
|
||||
#[test]
|
||||
fn every_field_of_a_schema_is_shown() {
|
||||
let markdown = reference(&[spec("test.full", &["View", "Saved"], Some(&FULL))]).unwrap();
|
||||
for (label, shown) in [
|
||||
("Label", "Label"),
|
||||
("Menu", "View › Saved"),
|
||||
("API level", "2"),
|
||||
("Deprecated", "API level 4"),
|
||||
("Writes", "preferences"),
|
||||
("Cost", "5"),
|
||||
("Summary", "do the thing"),
|
||||
("Returns", "{done}"),
|
||||
] {
|
||||
assert_eq!(item(&markdown, label), shown, "{label}");
|
||||
}
|
||||
let lenient = "lenient (a value of another type or out of range is passed to the command, not refused)";
|
||||
let expected: [[&str; 9]; 11] = [
|
||||
["zoom", "number", "more than 0, at most 64", "pt", "None", "Yes", "1", "None", "1 = 100%"],
|
||||
["name", "string", "None", "None", "the first free name (worked out by the command)", "No", "1", "None", "null reads as omitted"],
|
||||
["old", "boolean", "None", "None", "true (JSON)", "No", "2", "API level 3", lenient],
|
||||
["edits", "list of object", "None", "None", "None", "No", "1", "None", "None"],
|
||||
["edits[].size", "integer", "at least 1, at most 10", "None", "None", "Yes", "1", "None", "None"],
|
||||
["edits[].mode", "one of \"a\", \"b\"", "None", "None", "\"a\" (JSON)", "No", "1", "None", "None"],
|
||||
["ids", "list of object ids", "None", "None", "None", "No", "1", "None", "None"],
|
||||
["at", "point ([x, y], two numbers)", "None", "None", "None", "No", "1", "None", "None"],
|
||||
["color", "color (a string, [r, g, b] or an object)", "None", "None", "None", "No", "1", "None", "None"],
|
||||
["id", "object id (integer)", "at least 0, at most 9007199254740991", "None", "None", "No", "1", "None", "None"],
|
||||
["ratio", "number", "at least -1.5, less than 2", "None", "None", "No", "1", "None", "None"],
|
||||
];
|
||||
let names: Vec<String> =
|
||||
markdown.lines().filter(|l| l.starts_with("| ") && !l.starts_with("| Parameter")).map(|l| literal(&cells(l)[0])).collect();
|
||||
assert_eq!(names, expected.map(|r| r[0]));
|
||||
for want in expected {
|
||||
assert_eq!(row(&markdown, want[0]), want, "{}", want[0]);
|
||||
}
|
||||
}
|
||||
|
||||
const HOSTILE: &str = "a | b [link](https://evil.example) <script>x</script> `code` \\ *em* _u_  & ~~s~~ # h www.example.com me@example.com\r\nnext\tline";
|
||||
|
||||
static ESCAPES: ParamSchema =
|
||||
ParamSchema::new(&[Param::new("a|b", Type::String).doc(HOSTILE).defaults_to("\"<b>|</b>\"")], HOSTILE).returns(HOSTILE);
|
||||
|
||||
/// Markdown in a schema's text shows as plain text: a description stays in its own cell of a
|
||||
/// nine-cell row, every punctuation character is escaped, and line breaks and tabs are spaces.
|
||||
#[test]
|
||||
fn markdown_in_schema_text_stays_literal_in_its_cell() {
|
||||
let markdown = reference(&[spec("test.escapes", &["A|B", "[x](y)"], Some(&ESCAPES))]).unwrap();
|
||||
let shown = HOSTILE.replace(['\r', '\n', '\t'], " ");
|
||||
let cells = row(&markdown, "a|b");
|
||||
assert_eq!(cells[4], "\"<b>|</b>\" (JSON)");
|
||||
assert_eq!(cells[8], shown);
|
||||
assert_eq!(item(&markdown, "Summary"), shown);
|
||||
assert_eq!(item(&markdown, "Returns"), shown);
|
||||
assert_eq!(item(&markdown, "Menu"), "A|B › [x](y)");
|
||||
assert!(markdown.contains("www\\.example"), "{markdown}");
|
||||
let table: Vec<&str> = markdown.lines().filter(|l| l.starts_with('|')).collect();
|
||||
assert_eq!(table.len(), 3, "the header, the delimiter row and one row: {table:?}");
|
||||
assert!(!markdown.contains('\r') && !markdown.contains('\t'));
|
||||
}
|
||||
|
||||
/// A registry with no schema gives the header and a line that says so; a command without a
|
||||
/// schema adds nothing.
|
||||
#[test]
|
||||
fn a_registry_without_schemas_gives_a_document_that_says_so() {
|
||||
let empty = format!("{HEADER}\nNo commands have parameter schemas.\n");
|
||||
assert_eq!(reference(&[]).unwrap(), empty);
|
||||
assert_eq!(reference(&[spec("test.none", &["View"], None)]).unwrap(), empty);
|
||||
let one = reference(&[spec("test.none", &[], None), spec("test.full", &[], Some(&FULL)), spec("test.other", &[], None)]).unwrap();
|
||||
assert_eq!(one, reference(&[spec("test.full", &[], Some(&FULL))]).unwrap());
|
||||
}
|
||||
|
||||
/// A reference of exactly [`MAX_BYTES`] is returned; one byte more is an error naming the
|
||||
/// command, with no part of the reference.
|
||||
#[test]
|
||||
fn a_reference_past_the_limit_is_an_error() {
|
||||
let with = |summary: String| -> Result<String> {
|
||||
let schema: &'static ParamSchema = Box::leak(Box::new(ParamSchema::new(&[], Box::leak(summary.into_boxed_str()))));
|
||||
reference(&[spec("test.big", &[], Some(schema))])
|
||||
};
|
||||
let base = with("x".into()).unwrap().len();
|
||||
let fits = with("x".repeat(MAX_BYTES - base + 1)).unwrap();
|
||||
assert_eq!(fits.len(), MAX_BYTES);
|
||||
let e = with("x".repeat(MAX_BYTES - base + 2)).unwrap_err().to_string();
|
||||
assert!(e.contains(ID) && e.contains("larger than 1048576 bytes"), "{e}");
|
||||
assert!(e.len() < 200, "{e}");
|
||||
}
|
||||
|
||||
/// An unknown key is refused with a parameter error naming it, and the command needs no
|
||||
/// document.
|
||||
#[test]
|
||||
fn an_unknown_parameter_is_refused_naming_it() {
|
||||
let mut s = Session::new();
|
||||
let e = s.execute(ID, &json!({"prefix": "view."})).unwrap_err().to_string();
|
||||
assert!(e.contains(ID) && e.contains("`prefix`"), "{e}");
|
||||
assert!(s.execute(ID, &json!([])).is_err());
|
||||
assert!(s.active().is_none());
|
||||
assert!(s.execute(ID, &json!({})).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_marks_each_punctuation_character_and_flattens_control_characters() {
|
||||
assert_eq!(escape("a|b"), "a\\|b");
|
||||
assert_eq!(escape("WWW.x and w.x"), "WWW\\.x and w.x");
|
||||
assert_eq!(escape("é\u{7}\u{85}z"), "é z");
|
||||
assert_eq!(escape("1.5 > 1"), "1.5 \\> 1");
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,10 @@ use crate::cmd::{CommandSpec, bad, command_specs};
|
||||
use crate::{EngineError, Result};
|
||||
|
||||
/// The API level of the commands' parameters.
|
||||
pub const API_VERSION: u32 = 1;
|
||||
pub const API_VERSION: u32 = 2;
|
||||
|
||||
/// [`schema_hash`] at each API level, oldest first; the last row is [`API_VERSION`]'s.
|
||||
pub const SCHEMA_HASHES: &[(u32, u64)] = &[(1, 0x9cdb_1087_7074_855c)];
|
||||
pub const SCHEMA_HASHES: &[(u32, u64)] = &[(1, 0x9cdb_1087_7074_855c), (2, 0x0e31_5d96_7bc9_2653)];
|
||||
|
||||
/// The hash of every built-in command's schema ([`vectorcraft_apischema::hash`]).
|
||||
pub fn schema_hash() -> u64 {
|
||||
@@ -296,8 +296,12 @@ mod tests {
|
||||
("view.capsLock", json!([])),
|
||||
("view.saved.new", json!(5)),
|
||||
("view.transparencyGrid", json!("on")),
|
||||
("engine.apiReference", json!({})),
|
||||
("engine.apiReference", json!(null)),
|
||||
("engine.apiReference", json!({"prefix": "view."})),
|
||||
("engine.apiReference", json!([])),
|
||||
];
|
||||
assert_eq!(schemas().len(), 6);
|
||||
assert_eq!(schemas().len(), 7);
|
||||
for (id, schema) in schemas() {
|
||||
let written = schema.to_json();
|
||||
check_schema(&written, id).unwrap_or_else(|e| panic!("{e}\n{written:#}"));
|
||||
|
||||
@@ -18,6 +18,20 @@ pub const DOC_JSON_URI: &str = "vectorcraft://document/json";
|
||||
|
||||
/// The live command catalog, including enabled state.
|
||||
pub const COMMANDS_URI: &str = "vectorcraft://commands";
|
||||
/// The command API reference in Markdown (`engine.apiReference`).
|
||||
pub const REFERENCE_URI: &str = "vectorcraft://commands/reference";
|
||||
|
||||
/// The media type of the JSON resources.
|
||||
pub const JSON: &str = "application/json";
|
||||
/// The media type of the command API reference.
|
||||
pub const MARKDOWN: &str = "text/markdown; charset=utf-8";
|
||||
|
||||
/// What `resources/read` returns for a URI: the text and its media type.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Contents {
|
||||
pub mime: &'static str,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// Why a resource could not be read. The distinction matters: the server turns it into `-32002`
|
||||
/// (no such resource) or `-32602` (bad URI), and guessing from the message text would not hold.
|
||||
@@ -83,9 +97,24 @@ pub fn list() -> Value {
|
||||
{"uri": DOC_URI, "name": "document", "title": "Active document (summary)", "description": "Layer tree, artboards, selection and history of the active document (document.inspect)", "mimeType": "application/json"},
|
||||
{"uri": DOC_JSON_URI, "name": "document-json", "title": "Active document (full model)", "description": "The complete document model as JSON (document.json)", "mimeType": "application/json"},
|
||||
{"uri": COMMANDS_URI, "name": "commands", "title": "Command catalog", "description": "Command ids, labels, parameter descriptions and current enabled state", "mimeType": "application/json"},
|
||||
{"uri": REFERENCE_URI, "name": "command-reference", "title": "Command API reference", "description": "Markdown reference of the commands that have a parameter schema: types, ranges, units, defaults, API levels, what each writes and returns (engine.apiReference)", "mimeType": MARKDOWN},
|
||||
]})
|
||||
}
|
||||
|
||||
/// `resources/read`: the text behind a resource URI, the command API reference as Markdown and
|
||||
/// every other resource as indented JSON ([`read`]).
|
||||
pub fn read_contents(b: &mut dyn Backend, uri: &str) -> Result<Contents, ReadError> {
|
||||
if uri == REFERENCE_URI {
|
||||
let mut reply = exec(b, "engine.apiReference").map_err(ReadError::Backend)?;
|
||||
return match reply.get_mut("markdown").map(Value::take) {
|
||||
Some(Value::String(text)) => Ok(Contents { mime: MARKDOWN, text }),
|
||||
_ => Err(ReadError::Backend("engine.apiReference returned no `markdown` text".into())),
|
||||
};
|
||||
}
|
||||
let v = read(b, uri)?;
|
||||
Ok(Contents { mime: JSON, text: serde_json::to_string_pretty(&v).unwrap_or_default() })
|
||||
}
|
||||
|
||||
/// `resources/templates/list`.
|
||||
pub fn templates() -> Value {
|
||||
let list: Vec<Value> = TEMPLATES
|
||||
@@ -246,7 +275,7 @@ mod tests {
|
||||
#[test]
|
||||
fn lists_are_well_formed() {
|
||||
let l = list();
|
||||
assert_eq!(l["resources"].as_array().map(Vec::len), Some(3));
|
||||
assert_eq!(l["resources"].as_array().map(Vec::len), Some(4));
|
||||
let t = templates();
|
||||
let items = t["resourceTemplates"].as_array().expect("templates");
|
||||
assert_eq!(items.len(), TEMPLATES.len());
|
||||
@@ -257,6 +286,51 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A backend that gives every call the same reply.
|
||||
struct Fixed(Result<Value, String>);
|
||||
|
||||
impl Backend for Fixed {
|
||||
fn call(&mut self, _: &str, _: Value) -> Result<Value, String> {
|
||||
self.0.clone()
|
||||
}
|
||||
fn has_ui(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn describe(&self) -> String {
|
||||
"fixed".into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The command reference is listed and read as Markdown, with the text `engine.apiReference`
|
||||
/// returns, and with no document open; the other fixed resources still read as JSON.
|
||||
#[test]
|
||||
fn the_command_reference_reads_as_markdown() {
|
||||
let listed = list();
|
||||
let entry = listed["resources"].as_array().and_then(|r| r.iter().find(|r| r["uri"] == REFERENCE_URI)).expect("listed");
|
||||
assert_eq!(entry["mimeType"], MARKDOWN);
|
||||
assert_eq!(entry["name"], "command-reference");
|
||||
let mut b = Box::new(crate::Headless::new());
|
||||
let read = read_contents(b.as_mut(), REFERENCE_URI).unwrap();
|
||||
assert_eq!(read.mime, MARKDOWN);
|
||||
let direct = b.call("engine.execute", json!({"command": "engine.apiReference", "params": {}})).unwrap();
|
||||
assert_eq!(read.text, direct["markdown"].as_str().unwrap());
|
||||
assert!(read.text.starts_with("# VectorCraft command API reference\n"), "{}", read.text);
|
||||
assert_eq!(read_contents(b.as_mut(), REFERENCE_URI).unwrap(), read);
|
||||
let commands = read_contents(b.as_mut(), COMMANDS_URI).unwrap();
|
||||
assert_eq!(commands.mime, JSON);
|
||||
assert!(serde_json::from_str::<Value>(&commands.text).unwrap().is_array());
|
||||
assert!(matches!(read_contents(b.as_mut(), "vectorcraft://commands/nope"), Err(ReadError::NotFound(_))));
|
||||
}
|
||||
|
||||
/// A reply without the Markdown text, and an app that cannot be reached, are backend errors.
|
||||
#[test]
|
||||
fn a_bad_or_failed_reference_reply_is_a_backend_error() {
|
||||
for reply in [Ok(json!({})), Ok(json!({"markdown": 5})), Ok(json!("text")), Err("the app is not running".to_string())] {
|
||||
let mut b = Fixed(reply.clone());
|
||||
assert!(matches!(read_contents(&mut b, REFERENCE_URI), Err(ReadError::Backend(_))), "{reply:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_and_unknown_uris_are_errors() {
|
||||
let mut b = Box::new(crate::Headless::with_document());
|
||||
|
||||
@@ -439,12 +439,12 @@ impl Server {
|
||||
}
|
||||
"resources/read" => {
|
||||
let uri = params.get("uri").and_then(Value::as_str).ok_or((INVALID_PARAMS, "missing `uri`".to_string()))?;
|
||||
let v = resources::read(self.backend.as_mut(), uri).map_err(|e| match e {
|
||||
let read = resources::read_contents(self.backend.as_mut(), uri).map_err(|e| match e {
|
||||
resources::ReadError::NotFound(uri) => (RESOURCE_NOT_FOUND, format!("resource not found: {uri}")),
|
||||
resources::ReadError::Invalid(why) => (INVALID_PARAMS, why),
|
||||
resources::ReadError::Backend(why) => (INTERNAL_ERROR, why),
|
||||
})?;
|
||||
Ok(json!({"contents": [{"uri": uri, "mimeType": "application/json", "text": serde_json::to_string_pretty(&v).unwrap_or_default()}]}))
|
||||
Ok(json!({"contents": [{"uri": uri, "mimeType": read.mime, "text": read.text}]}))
|
||||
}
|
||||
"completion/complete" => Ok(prompts::complete(self.backend.as_mut(), params)),
|
||||
"logging/setLevel" => {
|
||||
|
||||
@@ -304,7 +304,7 @@ fn headless_end_to_end() {
|
||||
|
||||
// Resources.
|
||||
let v = rpc(&mut s, 10, "resources/list", json!({}));
|
||||
assert_eq!(v["result"]["resources"].as_array().unwrap().len(), 3);
|
||||
assert_eq!(v["result"]["resources"].as_array().unwrap().len(), 4);
|
||||
let v = rpc(&mut s, 11, "resources/read", json!({"uri": "vectorcraft://document"}));
|
||||
let text = v["result"]["contents"][0]["text"].as_str().unwrap();
|
||||
assert!(serde_json::from_str::<Value>(text).unwrap()["layers"].is_array());
|
||||
|
||||
@@ -290,8 +290,16 @@ fn resources_are_json() {
|
||||
let list = rpc(&mut s, 2, "resources/list", json!({}));
|
||||
for r in list["result"]["resources"].as_array().unwrap() {
|
||||
let v = rpc(&mut s, 3, "resources/read", json!({"uri": r["uri"]}));
|
||||
let text = v["result"]["contents"][0]["text"].as_str().unwrap();
|
||||
serde_json::from_str::<Value>(text).unwrap();
|
||||
let content = &v["result"]["contents"][0];
|
||||
assert_eq!(content["mimeType"], r["mimeType"], "{}", r["uri"]);
|
||||
assert_eq!(content["uri"], r["uri"]);
|
||||
let text = content["text"].as_str().unwrap();
|
||||
if r["mimeType"] == "application/json" {
|
||||
serde_json::from_str::<Value>(text).unwrap();
|
||||
} else {
|
||||
assert_eq!(r["mimeType"], "text/markdown; charset=utf-8", "{}", r["uri"]);
|
||||
assert!(text.starts_with("# ") && text.ends_with('\n'), "{}", r["uri"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -567,6 +567,7 @@ font-technology Variable Променливи
|
||||
Coming soon — tracked in the parity plan Очаквайте скоро — следи се в плана за съвместимост
|
||||
Command Click to Select Objects Behind Щракване с Command за избор на обекти отзад
|
||||
Command Dialog Диалогов прозорец за команда
|
||||
Command API Reference Справочник за API на командите
|
||||
Community Общност
|
||||
Compatibility: Съвместимост:
|
||||
Compatible Gradient and Gradient Mesh Printing Съвместим печат на градиент и градиентна мрежа
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -965,6 +965,7 @@ lighting Light Svjetlo
|
||||
Color Picker… Birač boja…
|
||||
Coming soon — tracked in the parity plan Uskoro — prati se u planu usklađenosti
|
||||
Command Dialog Dijalog naredbe
|
||||
Command API Reference Referenca API-ja naredbi
|
||||
Copy Selected Style Kopiraj odabrani stil
|
||||
Corners… Uglovi…
|
||||
Define Perspective Grid… Definiši mrežu perspektive…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -794,6 +794,7 @@ antiAlias Sharp Nítid
|
||||
Command Ordre
|
||||
Command Click to Select Objects Behind Clic amb Ordre per seleccionar els objectes del darrere
|
||||
Command Dialog Diàleg d’ordres
|
||||
Command API Reference Referència de l’API d’ordres
|
||||
Comment not closed Comentari no tancat
|
||||
Commenting, filling in form fields and signing Comentar, emplenar camps de formulari i signar
|
||||
Commits Commits
|
||||
@@ -5432,6 +5433,7 @@ digits Malayalam Malaiàlam
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out S’ha carregat “{name}”: s’ha omès {n} pas|S’ha carregat “{name}”: s’han omès {n} passos
|
||||
@msg not an action set file no és un fitxer de conjunt d’accions
|
||||
@msg the action set file is larger than 8 MiB el fitxer de conjunt d’accions supera els 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: la referència superaria els {MAX_BYTES} bytes
|
||||
@msg the action set file holds more than 1,000 actions el fitxer de conjunt d’accions conté més de 1.000 accions
|
||||
@msg the action set file needs a later version of VectorCraft el fitxer de conjunt d’accions necessita una versió més recent del VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps una acció del fitxer de conjunt d’accions té més de 10.000 passos
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -985,6 +985,7 @@ lighting Light Světlo
|
||||
Color Picker… Výběr barvy…
|
||||
Coming soon — tracked in the parity plan Připravujeme — sledováno v plánu parity
|
||||
Command Dialog Dialogové okno příkazu
|
||||
Command API Reference Reference API příkazů
|
||||
Copy Selected Style Kopírovat vybraný styl
|
||||
Corners… Rohy…
|
||||
Define Perspective Grid… Definovat perspektivní mřížku…
|
||||
@@ -4722,6 +4723,7 @@ imageTrace Outline Obrys
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Načteno „{name}“: vynechán {n} krok|Načteno „{name}“: vynechány {n} kroky|Načteno „{name}“: vynecháno {n} kroků
|
||||
@msg not an action set file není to soubor sady akcí
|
||||
@msg the action set file is larger than 8 MiB soubor sady akcí je větší než 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: reference by byla větší než {MAX_BYTES} bajtů
|
||||
@msg the action set file holds more than 1,000 actions soubor sady akcí obsahuje více než 1 000 akcí
|
||||
@msg the action set file needs a later version of VectorCraft soubor sady akcí vyžaduje novější verzi aplikace VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps akce v souboru sady akcí má více než 10 000 kroků
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -906,6 +906,7 @@ lighting Light Lys
|
||||
Color Picker… Farvevælger…
|
||||
Coming soon — tracked in the parity plan Kommer snart — følges i paritetsplanen
|
||||
Command Dialog Kommandodialog
|
||||
Command API Reference API-reference for kommandoer
|
||||
Copy Selected Style Kopiér markeret typografi
|
||||
Corners… Hjørner…
|
||||
Define Perspective Grid… Definér perspektivgitter…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -560,6 +560,7 @@ font-technology Variable Variabel
|
||||
Coming soon — tracked in the parity plan Demnächst verfügbar – im Paritätsplan vermerkt
|
||||
Command Click to Select Objects Behind Cmd-Klick wählt dahinterliegende Objekte aus
|
||||
Command Dialog Befehlsdialog
|
||||
Command API Reference API-Referenz der Befehle
|
||||
Community Community
|
||||
Compatibility: Kompatibilität:
|
||||
Compatible Gradient and Gradient Mesh Printing Kompatibles Drucken von Verläufen und Verlaufsgittern
|
||||
@@ -4329,6 +4330,7 @@ theme Appearance Erscheinungsbild
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out „{name}“ geladen: {n} Schritt wurde ausgelassen|„{name}“ geladen: {n} Schritte wurden ausgelassen
|
||||
@msg not an action set file keine Aktionssatzdatei
|
||||
@msg the action set file is larger than 8 MiB die Aktionssatzdatei ist größer als 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: die Referenz wäre größer als {MAX_BYTES} Byte
|
||||
@msg the action set file holds more than 1,000 actions die Aktionssatzdatei enthält mehr als 1.000 Aktionen
|
||||
@msg the action set file needs a later version of VectorCraft die Aktionssatzdatei erfordert eine neuere Version von VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps eine Aktion in der Aktionssatzdatei hat mehr als 10.000 Schritte
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -542,6 +542,7 @@ font-technology Variable Μεταβλητές
|
||||
Coming soon — tracked in the parity plan Προσεχώς — περιλαμβάνεται στο σχέδιο αντιστοιχίας λειτουργιών
|
||||
Command Click to Select Objects Behind Command-κλικ για επιλογή αντικειμένων από πίσω
|
||||
Command Dialog Παράθυρο εντολών
|
||||
Command API Reference Αναφορά API εντολών
|
||||
Community Κοινότητα
|
||||
Compatibility: Συμβατότητα:
|
||||
Compatible Gradient and Gradient Mesh Printing Συμβατή εκτύπωση ντεγκραντέ και πλέγματος ντεγκραντέ
|
||||
@@ -4367,6 +4368,7 @@ digits Malayalam Μαλαγιάλαμ
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Φορτώθηκε το «{name}»: παραλείφθηκε {n} βήμα|Φορτώθηκε το «{name}»: παραλείφθηκαν {n} βήματα
|
||||
@msg not an action set file δεν είναι αρχείο συνόλου ενεργειών
|
||||
@msg the action set file is larger than 8 MiB το αρχείο συνόλου ενεργειών είναι μεγαλύτερο από 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: η αναφορά θα ήταν μεγαλύτερη από {MAX_BYTES} byte
|
||||
@msg the action set file holds more than 1,000 actions το αρχείο συνόλου ενεργειών περιέχει περισσότερες από 1.000 ενέργειες
|
||||
@msg the action set file needs a later version of VectorCraft το αρχείο συνόλου ενεργειών απαιτεί νεότερη έκδοση του VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps μια ενέργεια στο αρχείο συνόλου ενεργειών έχει περισσότερα από 10.000 βήματα
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -582,6 +582,7 @@ font-technology Variable Variables
|
||||
Coming soon — tracked in the parity plan Próximamente: incluido en el plan de paridad
|
||||
Command Click to Select Objects Behind Cmd+clic para seleccionar objetos de detrás
|
||||
Command Dialog Cuadro de diálogo de comando
|
||||
Command API Reference Referencia de la API de comandos
|
||||
Community Comunidad
|
||||
Compatibility: Compatibilidad:
|
||||
Compatible Gradient and Gradient Mesh Printing Impresión compatible de degradados y mallas de degradado
|
||||
@@ -4413,6 +4414,7 @@ axis Both Ambos
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Se cargó “{name}”: se omitió {n} paso|Se cargó “{name}”: se omitieron {n} pasos
|
||||
@msg not an action set file no es un archivo de conjunto de acciones
|
||||
@msg the action set file is larger than 8 MiB el archivo de conjunto de acciones supera los 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: la referencia superaría los {MAX_BYTES} bytes
|
||||
@msg the action set file holds more than 1,000 actions el archivo de conjunto de acciones contiene más de 1000 acciones
|
||||
@msg the action set file needs a later version of VectorCraft el archivo de conjunto de acciones necesita una versión más reciente de VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps una acción del archivo de conjunto de acciones tiene más de 10 000 pasos
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -964,6 +964,7 @@ lighting Light Valo
|
||||
Color Picker… Värivalitsin…
|
||||
Coming soon — tracked in the parity plan Tulossa pian — seurataan yhdenmukaisuussuunnitelmassa
|
||||
Command Dialog Komentovalintaikkuna
|
||||
Command API Reference Komentojen API-viite
|
||||
Copy Selected Style Kopioi valittu tyyli
|
||||
Corners… Kulmat…
|
||||
Define Perspective Grid… Määritä perspektiiviruudukko…
|
||||
@@ -4630,6 +4631,7 @@ imageTrace Outline Ääriviiva
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Ladattiin ”{name}”: jätettiin pois {n} vaihe|Ladattiin ”{name}”: jätettiin pois {n} vaihetta
|
||||
@msg not an action set file ei ole toimintosarjatiedosto
|
||||
@msg the action set file is larger than 8 MiB toimintosarjatiedosto on suurempi kuin 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: viite olisi suurempi kuin {MAX_BYTES} tavua
|
||||
@msg the action set file holds more than 1,000 actions toimintosarjatiedostossa on yli 1 000 toimintoa
|
||||
@msg the action set file needs a later version of VectorCraft toimintosarjatiedosto vaatii uudemman VectorCraft-version
|
||||
@msg an action in the action set file has more than 10,000 steps toimintosarjatiedoston toiminnossa on yli 10 000 vaihetta
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -583,6 +583,7 @@ font-technology Variable Variables
|
||||
Coming soon — tracked in the parity plan Bientôt disponible — prévu dans le plan de parité
|
||||
Command Click to Select Objects Behind Cmd+clic pour sélectionner les objets situés derrière
|
||||
Command Dialog Boîte de dialogue des commandes
|
||||
Command API Reference Référence de l’API des commandes
|
||||
Community Communauté
|
||||
Compatibility: Compatibilité :
|
||||
Compatible Gradient and Gradient Mesh Printing Impression compatible des dégradés et des filets de dégradé
|
||||
@@ -4402,6 +4403,7 @@ axis Both Les deux
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out « {name} » chargé : {n} étape a été ignorée|« {name} » chargé : {n} étapes ont été ignorées
|
||||
@msg not an action set file ce n’est pas un fichier de jeu de scripts
|
||||
@msg the action set file is larger than 8 MiB le fichier de jeu de scripts dépasse 8 Mio
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}` : la référence dépasserait {MAX_BYTES} octets
|
||||
@msg the action set file holds more than 1,000 actions le fichier de jeu de scripts contient plus de 1 000 scripts
|
||||
@msg the action set file needs a later version of VectorCraft le fichier de jeu de scripts nécessite une version plus récente de VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps un script du fichier de jeu de scripts compte plus de 10 000 étapes
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -896,6 +896,7 @@ antiAlias Sharp शार्प
|
||||
Command कमांड
|
||||
Command Click to Select Objects Behind कमांड पीछे ऑब्जेक्ट्स का सिलेक्ट करने के लिए क्लिक करें
|
||||
Command Dialog कमान डायलॉग
|
||||
Command API Reference कमांड API संदर्भ
|
||||
Comment not closed टिप्पणी बंद नहीं
|
||||
Commenting, filling in form fields and signing टिप्पणी करना, फॉर्म फ़ील्ड में भरना और हस्ताक्षर करना
|
||||
Commits कमिट्स
|
||||
@@ -7385,6 +7386,7 @@ antiAlias Sharp शार्प
|
||||
@msg the action has no steps कार्रवाई में कोई कदम नहीं है
|
||||
@msg the action set file holds more than 1,000 actions एक्शन सेट फ़ाइल 1,000 से अधिक कार्यों को रखती है
|
||||
@msg the action set file is larger than 8 MiB एक्शन सेट फ़ाइल 8 MiB से बड़ा है
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: संदर्भ {MAX_BYTES} बाइट से बड़ा होगा
|
||||
@msg the action set file needs a later version of VectorCraft एक्शन सेट फ़ाइल को वेक्टर क्रेफ्ट के बाद के संस्करण की आवश्यकता होती है
|
||||
@msg the action set ids are not a list कार्रवाई सेट ids एक सूची नहीं हैं
|
||||
@msg the action sets changed; nothing was deleted कार्रवाई सेट बदल गया; कुछ भी नहीं हटा दिया गया था
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -760,6 +760,7 @@ font-technology Variable Változó
|
||||
Command Parancs
|
||||
Command Click to Select Objects Behind Cmd+kattintás a mögöttes objektumok kijelöléséhez
|
||||
Command Dialog Parancs párbeszédablaka
|
||||
Command API Reference Parancsok API-referenciája
|
||||
Commenting, filling in form fields and signing Megjegyzések, űrlapmezők kitöltése és aláírás
|
||||
Commits Commitok
|
||||
Community Közösség
|
||||
@@ -5095,6 +5096,7 @@ imageTrace Outline Körvonal
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out „{name}” betöltve; kihagyott lépések: {n}
|
||||
@msg not an action set file nem műveletkészlet-fájl
|
||||
@msg the action set file is larger than 8 MiB a műveletkészlet-fájl nagyobb 8 MiB-nál
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: a referencia nagyobb lenne {MAX_BYTES} bájtnál
|
||||
@msg the action set file holds more than 1,000 actions a műveletkészlet-fájl több mint 1000 műveletet tartalmaz
|
||||
@msg the action set file needs a later version of VectorCraft a műveletkészlet-fájlhoz a VectorCraft újabb verziója kell
|
||||
@msg an action in the action set file has more than 10,000 steps a műveletkészlet-fájl egyik műveletében több mint 10 000 lépés van
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -906,6 +906,7 @@ lighting Light Cahaya
|
||||
Color Picker… Pemilih warna…
|
||||
Coming soon — tracked in the parity plan Segera hadir — dilacak dalam rencana paritas
|
||||
Command Dialog Dialog perintah
|
||||
Command API Reference Referensi API perintah
|
||||
Copy Selected Style Salin gaya terpilih
|
||||
Corners… Sudut…
|
||||
Define Perspective Grid… Definisikan kisi perspektif…
|
||||
|
||||
|
@@ -582,6 +582,7 @@ font-technology Variable Variabili
|
||||
Coming soon — tracked in the parity plan Prossimamente — previsto nel piano di parità
|
||||
Command Click to Select Objects Behind Cmd+clic per selezionare gli oggetti retrostanti
|
||||
Command Dialog Finestra dei comandi
|
||||
Command API Reference Riferimento API dei comandi
|
||||
Community Comunità
|
||||
Compatibility: Compatibilità:
|
||||
Compatible Gradient and Gradient Mesh Printing Stampa compatibile di sfumature e trame sfumatura
|
||||
@@ -4455,6 +4456,7 @@ axis Both Entrambi
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Caricato “{name}”: {n} passaggio è stato omesso|Caricato “{name}”: {n} passaggi sono stati omessi
|
||||
@msg not an action set file non è un file di set di azioni
|
||||
@msg the action set file is larger than 8 MiB il file del set di azioni supera 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: il riferimento supererebbe {MAX_BYTES} byte
|
||||
@msg the action set file holds more than 1,000 actions il file del set di azioni contiene più di 1.000 azioni
|
||||
@msg the action set file needs a later version of VectorCraft il file del set di azioni richiede una versione più recente di VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps un’azione nel file del set di azioni ha più di 10.000 passaggi
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1253,6 +1253,7 @@ antiAlias Sharp シャープ
|
||||
Coming soon — tracked in the parity plan 近日対応予定
|
||||
Command Click to Select Objects Behind Command+クリックで背面のオブジェクトを選択
|
||||
Command Dialog コマンドダイアログ
|
||||
Command API Reference コマンド API リファレンス
|
||||
Community コミュニティ
|
||||
Compatibility: 互換性:
|
||||
Compatible Gradient and Gradient Mesh Printing 互換性のあるグラデーションとグラデーションメッシュのプリント
|
||||
@@ -4801,6 +4802,7 @@ swatch Bright Violet ビビッドバイオレット
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out 「{name}」を読み込みました。{n} 個のステップは省きました
|
||||
@msg not an action set file アクションセットファイルではありません
|
||||
@msg the action set file is larger than 8 MiB アクションセットファイルが 8 MiB を超えています
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: リファレンスが {MAX_BYTES} バイトを超えます
|
||||
@msg the action set file holds more than 1,000 actions アクションセットファイルのアクションが 1,000 個を超えています
|
||||
@msg the action set file needs a later version of VectorCraft このアクションセットファイルには新しいバージョンの VectorCraft が必要です
|
||||
@msg an action in the action set file has more than 10,000 steps アクションセットファイルに 10,000 ステップを超えるアクションがあります
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -858,6 +858,7 @@ lighting Light სინათლე
|
||||
Color Picker… ფერის ამრჩევი…
|
||||
Coming soon — tracked in the parity plan მალე — აღრიცხულია თანხვედრის გეგმაში
|
||||
Command Dialog ბრძანების დიალოგი
|
||||
Command API Reference ბრძანებების API ცნობარი
|
||||
Copy Selected Style მონიშნული სტილის კოპირება
|
||||
Corners… კუთხეები…
|
||||
Define Perspective Grid… პერსპექტივის ბადის განსაზღვრა…
|
||||
@@ -4180,6 +4181,7 @@ font-technology Variable ცვლადი
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out ჩაიტვირთა „{name}“; გამოტოვებული ნაბიჯები: {n}
|
||||
@msg not an action set file ეს არ არის მოქმედებების ნაკრების ფაილი
|
||||
@msg the action set file is larger than 8 MiB მოქმედებების ნაკრების ფაილი 8 MiB-ზე დიდია
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: ცნობარი {MAX_BYTES} ბაიტზე დიდი იქნებოდა
|
||||
@msg the action set file holds more than 1,000 actions მოქმედებების ნაკრების ფაილი 1000-ზე მეტ მოქმედებას შეიცავს
|
||||
@msg the action set file needs a later version of VectorCraft მოქმედებების ნაკრების ფაილს VectorCraft-ის უფრო ახალი ვერსია სჭირდება
|
||||
@msg an action in the action set file has more than 10,000 steps მოქმედებების ნაკრების ფაილში ერთ-ერთ მოქმედებას 10 000-ზე მეტი ნაბიჯი აქვს
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1243,6 +1243,7 @@ antiAlias Sharp 날카롭게
|
||||
Coming soon — tracked in the parity plan 곧 제공 예정 — 기능 동등성 계획에서 관리 중
|
||||
Command Click to Select Objects Behind Command-클릭으로 뒤쪽 개체 선택
|
||||
Command Dialog 명령 대화상자
|
||||
Command API Reference 명령 API 참조
|
||||
Community 커뮤니티
|
||||
Compatibility: 호환성:
|
||||
Compatible Gradient and Gradient Mesh Printing 호환 그레이디언트 및 그레이디언트 메시 인쇄
|
||||
@@ -4728,6 +4729,7 @@ theme Appearance 외관
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out “{name}”을(를) 불러왔습니다. 단계 {n}개를 제외했습니다
|
||||
@msg not an action set file 액션 세트 파일이 아닙니다
|
||||
@msg the action set file is larger than 8 MiB 액션 세트 파일이 8 MiB보다 큽니다
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: 참조가 {MAX_BYTES}바이트보다 커집니다
|
||||
@msg the action set file holds more than 1,000 actions 액션 세트 파일의 액션이 1,000개를 넘습니다
|
||||
@msg the action set file needs a later version of VectorCraft 이 액션 세트 파일을 열려면 최신 버전의 VectorCraft가 필요합니다
|
||||
@msg an action in the action set file has more than 10,000 steps 액션 세트 파일에 단계가 10,000개를 넘는 액션이 있습니다
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -920,6 +920,7 @@ lighting Light Licht
|
||||
Color Picker… Kleurkiezer…
|
||||
Coming soon — tracked in the parity plan Binnenkort beschikbaar — opgenomen in het pariteitsplan
|
||||
Command Dialog Opdrachtdialoogvenster
|
||||
Command API Reference API-naslag voor opdrachten
|
||||
Copy Selected Style Geselecteerde stijl kopiëren
|
||||
Corners… Hoeken…
|
||||
Define Perspective Grid… Perspectiefraster definiëren…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -921,6 +921,7 @@ lighting Light Światło
|
||||
Color Picker… Próbnik kolorów…
|
||||
Coming soon — tracked in the parity plan Już wkrótce — ujęte w planie zgodności
|
||||
Command Dialog Okno polecenia
|
||||
Command API Reference Dokumentacja API poleceń
|
||||
Copy Selected Style Kopiuj zaznaczony styl
|
||||
Corners… Narożniki…
|
||||
Define Perspective Grid… Zdefiniuj siatkę perspektywy…
|
||||
@@ -4545,6 +4546,7 @@ imageTrace Outline Kontur
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Wczytano „{name}”: pominięto {n} krok|Wczytano „{name}”: pominięto {n} kroki|Wczytano „{name}”: pominięto {n} kroków
|
||||
@msg not an action set file to nie jest plik zestawu operacji
|
||||
@msg the action set file is larger than 8 MiB plik zestawu operacji jest większy niż 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: dokumentacja byłaby większa niż {MAX_BYTES} bajtów
|
||||
@msg the action set file holds more than 1,000 actions plik zestawu operacji zawiera ponad 1000 operacji
|
||||
@msg the action set file needs a later version of VectorCraft plik zestawu operacji wymaga nowszej wersji programu VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps operacja w pliku zestawu operacji ma ponad 10 000 kroków
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -532,6 +532,7 @@ antiAlias Sharp Nítido
|
||||
Coming soon — tracked in the parity plan Em breve — rastreado no plano de paridade
|
||||
Command Click to Select Objects Behind Clique com Comando para Selecionar Objetos Atrás
|
||||
Command Dialog Diálogo de Comandos
|
||||
Command API Reference Referência da API de Comandos
|
||||
Community Comunidade
|
||||
Compatibility: Compatibilidade:
|
||||
Compatible Gradient and Gradient Mesh Printing Impressão Compatível de Gradiente e Malha de Gradiente
|
||||
@@ -3985,6 +3986,7 @@ axis Both Ambos
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out “{name}” carregado: {n} etapa foi omitida|“{name}” carregado: {n} etapas foram omitidas
|
||||
@msg not an action set file não é um arquivo de conjunto de ações
|
||||
@msg the action set file is larger than 8 MiB o arquivo de conjunto de ações tem mais de 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: a referência teria mais de {MAX_BYTES} bytes
|
||||
@msg the action set file holds more than 1,000 actions o arquivo de conjunto de ações contém mais de 1.000 ações
|
||||
@msg the action set file needs a later version of VectorCraft o arquivo de conjunto de ações requer uma versão mais recente do VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps uma ação do arquivo de conjunto de ações tem mais de 10.000 etapas
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -580,6 +580,7 @@ font-technology Variable Вариативные
|
||||
Coming soon — tracked in the parity plan Скоро — отслеживается в плане развития
|
||||
Command Click to Select Objects Behind Cmd+щелчок для выбора объектов позади
|
||||
Command Dialog Диалоговое окно команды
|
||||
Command API Reference Справочник по API команд
|
||||
Community Сообщество
|
||||
Compatibility: Совместимость:
|
||||
Compatible Gradient and Gradient Mesh Printing Совместимая печать градиентов и градиентных сеток
|
||||
@@ -4449,6 +4450,7 @@ axis Both Оба
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Загружено «{name}»: пропущен {n} шаг|Загружено «{name}»: пропущено {n} шага|Загружено «{name}»: пропущено {n} шагов
|
||||
@msg not an action set file это не файл набора действий
|
||||
@msg the action set file is larger than 8 MiB файл набора действий больше 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: справочник был бы больше {MAX_BYTES} байт
|
||||
@msg the action set file holds more than 1,000 actions файл набора действий содержит более 1000 действий
|
||||
@msg the action set file needs a later version of VectorCraft для файла набора действий нужна более новая версия VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps в файле набора действий есть действие более чем из 10 000 шагов
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -569,6 +569,7 @@ font-technology Variable Variabla
|
||||
Coming soon — tracked in the parity plan Kommer snart — följs upp i paritetsplanen
|
||||
Command Click to Select Objects Behind Cmd-klicka för att markera objekt bakom
|
||||
Command Dialog Kommandodialog
|
||||
Command API Reference API-referens för kommandon
|
||||
Community Gemenskap
|
||||
Compatibility: Kompatibilitet:
|
||||
Compatible Gradient and Gradient Mesh Printing Kompatibel utskrift av gradienter och gradientnät
|
||||
@@ -4317,6 +4318,7 @@ axis Both Båda
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Läste in ”{name}”: {n} steg utelämnades|Läste in ”{name}”: {n} steg utelämnades
|
||||
@msg not an action set file inte en fil med åtgärdsuppsättning
|
||||
@msg the action set file is larger than 8 MiB filen med åtgärdsuppsättningen är större än 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: referensen skulle bli större än {MAX_BYTES} byte
|
||||
@msg the action set file holds more than 1,000 actions filen med åtgärdsuppsättningen innehåller fler än 1 000 åtgärder
|
||||
@msg the action set file needs a later version of VectorCraft filen med åtgärdsuppsättningen kräver en nyare version av VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps en åtgärd i filen med åtgärdsuppsättningen har fler än 10 000 steg
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -563,6 +563,7 @@ font-technology Variable Değişken
|
||||
Coming soon — tracked in the parity plan Çok yakında — eşlik planında takip ediliyor
|
||||
Command Click to Select Objects Behind Komut Arkasındaki Nesneleri Seçmek İçin Tıklayın
|
||||
Command Dialog Komut İletişim Kutusu
|
||||
Command API Reference Komut API Başvurusu
|
||||
Community Topluluk
|
||||
Compatibility: Uyumluluk:
|
||||
Compatible Gradient and Gradient Mesh Printing Uyumlu Degrade ve Degrade Mesh Baskı
|
||||
@@ -4177,6 +4178,7 @@ axis Both Her İkisi
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out “{name}” yüklendi: {n} adım atlandı
|
||||
@msg not an action set file bir eylem seti dosyası değil
|
||||
@msg the action set file is larger than 8 MiB eylem seti dosyası 8 MiB'den büyük
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: başvuru {MAX_BYTES} bayttan büyük olur
|
||||
@msg the action set file holds more than 1,000 actions eylem seti dosyası 1.000'den fazla eylem içeriyor
|
||||
@msg the action set file needs a later version of VectorCraft eylem seti dosyası VectorCraft'ın daha yeni bir sürümünü gerektiriyor
|
||||
@msg an action in the action set file has more than 10,000 steps eylem seti dosyasındaki bir eylemde 10.000'den fazla adım var
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -576,6 +576,7 @@ font-technology Variable Варіативні
|
||||
Coming soon — tracked in the parity plan Незабаром відстежується в плані розвитку
|
||||
Command Click to Select Objects Behind Cmd+клацання для вибору об’єктів позаду
|
||||
Command Dialog Діалогове вікно команди
|
||||
Command API Reference Довідник API команд
|
||||
Community Спільнота
|
||||
Compatibility: Сумісність:
|
||||
Compatible Gradient and Gradient Mesh Printing Сумісний друк градієнтів та градієнтних сіток
|
||||
@@ -4439,6 +4440,7 @@ axis Both Обидві
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Завантажено «{name}»: пропущено {n} крок|Завантажено «{name}»: пропущено {n} кроки|Завантажено «{name}»: пропущено {n} кроків
|
||||
@msg not an action set file це не файл набору дій
|
||||
@msg the action set file is larger than 8 MiB файл набору дій більший за 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: довідник був би більшим за {MAX_BYTES} байтів
|
||||
@msg the action set file holds more than 1,000 actions файл набору дій містить понад 1000 дій
|
||||
@msg the action set file needs a later version of VectorCraft для файлу набору дій потрібна новіша версія VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps у файлі набору дій є дія з понад 10 000 кроків
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 182 and column 5.
|
@@ -671,6 +671,7 @@ font-technology Variable Biến thiên
|
||||
Coming soon — tracked in the parity plan Sắp ra mắt — đang theo dõi trong kế hoạch đối chiếu tính năng
|
||||
Command Click to Select Objects Behind Command-bấm để chọn đối tượng phía sau
|
||||
Command Dialog Hộp thoại lệnh
|
||||
Command API Reference Tài liệu tham khảo API lệnh
|
||||
Commits Commit
|
||||
Community Cộng đồng
|
||||
Company Công ty
|
||||
@@ -4748,6 +4749,7 @@ theme Appearance Giao diện
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out Đã tải “{name}”: bỏ qua {n} bước
|
||||
@msg not an action set file không phải tệp bộ hành động
|
||||
@msg the action set file is larger than 8 MiB tệp bộ hành động lớn hơn 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`: tài liệu tham khảo sẽ lớn hơn {MAX_BYTES} byte
|
||||
@msg the action set file holds more than 1,000 actions tệp bộ hành động chứa hơn 1.000 hành động
|
||||
@msg the action set file needs a later version of VectorCraft tệp bộ hành động cần phiên bản VectorCraft mới hơn
|
||||
@msg an action in the action set file has more than 10,000 steps một hành động trong tệp bộ hành động có hơn 10.000 bước
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 204 and column 5.
|
@@ -528,6 +528,7 @@ antiAlias Sharp 锐利
|
||||
Coming soon — tracked in the parity plan 即将推出 — 在对标计划中跟踪
|
||||
Command Click to Select Objects Behind 按住 Command 单击选择后方对象
|
||||
Command Dialog 命令对话框
|
||||
Command API Reference 命令 API 参考
|
||||
Community 社区
|
||||
Compatibility: 兼容性:
|
||||
Compatible Gradient and Gradient Mesh Printing 兼容渐变和渐变网格打印
|
||||
@@ -4508,6 +4509,7 @@ imageTrace Outline 轮廓
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out 已载入“{name}”:已略过 {n} 个步骤
|
||||
@msg not an action set file 不是动作组文件
|
||||
@msg the action set file is larger than 8 MiB 动作组文件大于 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`:参考将大于 {MAX_BYTES} 字节
|
||||
@msg the action set file holds more than 1,000 actions 动作组文件包含超过 1,000 个动作
|
||||
@msg the action set file needs a later version of VectorCraft 动作组文件需要更高版本的 VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps 动作组文件中有动作超过 10,000 个步骤
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -544,6 +544,7 @@ font-technology Variable 可變
|
||||
Coming soon — tracked in the parity plan 即將推出 — 已列入功能對等計畫
|
||||
Command Click to Select Objects Behind Cmd 加按一下可選取後方物件
|
||||
Command Dialog 指令對話框
|
||||
Command API Reference 指令 API 參考
|
||||
Community 社群
|
||||
Compatibility: 相容性:
|
||||
Compatible Gradient and Gradient Mesh Printing 相容的漸層與漸層網格列印
|
||||
@@ -2352,6 +2353,7 @@ file-filter Templates 範本
|
||||
@msgplural Loaded “{name}”: {n} step was left out|Loaded “{name}”: {n} steps were left out 已載入「{name}」:已略過 {n} 個步驟
|
||||
@msg not an action set file 不是動作組檔案
|
||||
@msg the action set file is larger than 8 MiB 動作組檔案大於 8 MiB
|
||||
@msg `{ID}`: the reference would be larger than {MAX_BYTES} bytes `{ID}`:參考將大於 {MAX_BYTES} 位元組
|
||||
@msg the action set file holds more than 1,000 actions 動作組檔案包含超過 1,000 個動作
|
||||
@msg the action set file needs a later version of VectorCraft 動作組檔案需要較新版本的 VectorCraft
|
||||
@msg an action in the action set file has more than 10,000 steps 動作組檔案中有動作超過 10,000 個步驟
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
+25
-6
@@ -210,8 +210,10 @@ echo '{"jsonrpc":"2.0","id":1,"method":"prompts/get","params":{"name":"poster","
|
||||
|
||||
### Resource templates
|
||||
|
||||
`vectorcraft://document` (summary), `vectorcraft://document/json` (the whole model), and
|
||||
`vectorcraft://commands` (the live command catalog with enabled state) are the fixed resources. The templates read one thing at a time, which matters on a real document: the full model
|
||||
`vectorcraft://document` (summary), `vectorcraft://document/json` (the whole model),
|
||||
`vectorcraft://commands` (the live command catalog with enabled state) and `vectorcraft://commands/reference` (the
|
||||
command API reference in Markdown, `text/markdown; charset=utf-8`; see [Parameter schemas](#parameter-schemas)) are
|
||||
the fixed resources. The templates read one thing at a time, which matters on a real document: the full model
|
||||
is thousands of lines an agent pays for again on every change.
|
||||
|
||||
| `uriTemplate` | Reads |
|
||||
@@ -748,6 +750,7 @@ Fixed resources and the four templates (same as [Resource templates](#resource-t
|
||||
| `vectorcraft://document` | `document.inspect` summary (JSON) |
|
||||
| `vectorcraft://document/json` | The complete document model (JSON) |
|
||||
| `vectorcraft://commands` | Live command catalog with enabled state |
|
||||
| `vectorcraft://commands/reference` | The command API reference (`engine.apiReference`), as Markdown (`text/markdown; charset=utf-8`) |
|
||||
| `vectorcraft://object/{id}` | One layer or object (`document.node {id, summary: true}`) |
|
||||
| `vectorcraft://command/{id}` | One command's metadata and enablement |
|
||||
| `vectorcraft://effect/{id}` | One live effect with parameters and defaults |
|
||||
@@ -1385,7 +1388,7 @@ vectorcraft-cli run --cmd file.new --params '{"width":600,"height":200}' --cmd
|
||||
### Parameter schemas
|
||||
|
||||
Some commands have a machine-readable parameter schema: at present `view.capsLock`, `view.saved.new`,
|
||||
`view.saved.edit`, `view.saved.list`, `view.transparencyGrid` and `view.snapToGlyph.options`. The engine's
|
||||
`view.saved.edit`, `view.saved.list`, `view.transparencyGrid`, `view.snapToGlyph.options` and `engine.apiReference`. The engine's
|
||||
`engine.commands` list gives such a command a `schema` field: an object schema in the shape of JSON Schema 2020-12
|
||||
(https://json-schema.org/draft/2020-12). Its root has `$schema` (the 2020-12 dialect), `description` when the
|
||||
command has a summary, and `deprecated: true` when the command is deprecated. The schema uses `type`, `properties`, `required` and
|
||||
@@ -1427,9 +1430,25 @@ path to a value inside a list or an object, and the document and its undo histor
|
||||
|
||||
An omitted value with a `default` takes it (`rotation` 0 in `view.saved.new`); the journal keeps the params as they
|
||||
were given. Saved steps played from a journal or an action keep a key the schema does not name, unless its value nests
|
||||
more than 16 levels deep, and the values of the parameters it names are checked as in a new call. The schemas of built-in commands belong to API level 1
|
||||
(`vectorcraft_engine::schemas::API_VERSION`); a change to any of them raises the level. Commands that plug-ins add have
|
||||
no schema here.
|
||||
more than 16 levels deep, and the values of the parameters it names are checked as in a new call. The schemas of built-in commands belong to API level 2
|
||||
(`vectorcraft_engine::schemas::API_VERSION`); a change to any of them raises the level. Level 2 added
|
||||
`engine.apiReference`. Commands that plug-ins add have no schema here.
|
||||
|
||||
`engine.apiReference {}` → `{markdown}` returns the command API reference, written from these schemas each time it
|
||||
runs; no file of it is kept. It needs no open document and changes nothing. The reference has a `##` section for each
|
||||
built-in command that has a schema, in registry order (the order `engine.commands` lists them), and leaves out every
|
||||
command without one, plug-in commands included; `engine.commands` still lists those with their params text. A section
|
||||
lists the command's label, menu path, API level, deprecation level, what it writes (`x-writes`), cost, summary and
|
||||
return, then a table of its parameters in the schema's order with the columns Parameter, Type, Range, Unit, Default,
|
||||
Required, Since, Deprecated and Description. A member of an object parameter is a row of its own after it, named with
|
||||
dots and `[]` for a list (`edits[].name`). A range says whether each end is included ("more than 0", "at most 64"); a
|
||||
default is either the JSON value the engine fills in, marked "(JSON)", or the note of a default the command works out;
|
||||
the Description says when null reads as omitted and when the engine does not refuse a value of another type (a lenient
|
||||
parameter). Text from a schema is escaped as Markdown text: each of `` \ | [ ] < > ` * _ ! & # ~ : @ `` and the `.`
|
||||
after `www` get a backslash, and line breaks, tabs and other control characters become spaces, so a description cannot
|
||||
end a table cell or make a link, an image or HTML. A reference larger than 1 MiB (1,048,576 bytes) is an error naming
|
||||
`engine.apiReference`, and no part of it is returned. MCP serves the same text as the resource
|
||||
`vectorcraft://commands/reference`.
|
||||
|
||||
## Checking scripts written for the reference app
|
||||
|
||||
|
||||
@@ -674,6 +674,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Architecture:** 19+ crates with enforced layering (`cargo xtask layers`). Every action is a command (~400 engine + ~50 UI). Undo is unlimited via structural sharing. `command.batch` runs several commands as one transaction.
|
||||
- **Parameter schemas:** a command can carry a machine-readable parameter schema (the new `vectorcraft-apischema` crate): each parameter's type, range, unit, whether it is required, its default, whether null counts as left out, and the API levels it was added and deprecated at. `engine.commands` lists the schema in the shape of a JSON Schema 2020-12 object schema, a test keeps the command's `params` text equal to the text the schema writes, and a new call has the params checked against it before the command runs (batch steps before the first step), with an error naming the parameter and no change to the document; saved steps keep a key the schema does not name, and their values are checked. The View commands `view.capsLock`, `view.saved.new`, `view.saved.edit`, `view.saved.list`, `view.transparencyGrid` and `view.snapToGlyph.options` have schemas. The schemas belong to API level 1 (`vectorcraft_engine::schemas::API_VERSION`), and a test fails when a schema changes without a new level. Tests: `crates/apischema/src/tests.rs` (with `conformance.rs`, a separate check of the JSON form against the JSON Schema 2020-12 rules), `schemas::tests` and `cmd::views::tests::each_view_command_refuses_a_bad_value_naming_the_parameter` in `vectorcraft-engine`.
|
||||
- **Input schemas in the command listings:** `engine.commands` and the MCP tools `command_list` and `list_commands` give each command with a parameter schema an `inputSchema` next to `schema`: the same schema with the root `type` `"object"`, the form an MCP tool's input schema takes ([MCP schema reference](https://modelcontextprotocol.io/specification/2026-07-28/schema)). A command without a schema lists neither, and `brief` leaves out both schemas with the `params` text. Tests: `schemas::tests::engine_commands_list_an_input_schema_with_an_object_root` and `schemas::tests::an_input_schema_keeps_deprecation` in `vectorcraft-engine`, and `tests::conventions::command_listings_carry_input_schemas` in `vectorcraft-mcp`, which check every listed input schema against the JSON Schema 2020-12 rules.
|
||||
- **Command API reference:** `engine.apiReference` returns a Markdown reference written from the parameter schemas when it is asked for, and MCP serves the same text as the resource `vectorcraft://commands/reference` (`text/markdown; charset=utf-8`). Each command that has a schema gets a section in registry order with its label, menu path, API levels, what it writes, summary and return, and a table of its parameters (type, range with its ends marked included or not, unit, default, required, levels and description; members of objects as dotted rows). Commands without a schema, plug-in commands included, are left out. Schema text is escaped so it stays plain text in its table cell, the same build always returns the same bytes, and a reference past 1 MiB is an error rather than a cut-short text. The command's own schema raised the API level to 2. This is a VectorCraft documentation feature; no score changes. Tests: `cmd::apiref::tests` and `schemas::tests` in `vectorcraft-engine`, `resources::tests` in `vectorcraft-mcp`.
|
||||
- **Automation:**
|
||||
- Variables: Update Data Set writes what the art holds into the active data set for the variables that data set holds, and keeps the data set's value for a text, image or graph variable whose objects are locked or inside a locked layer or group; `dataset.update {all: true}` also adds the other variables that the art reports a value for.
|
||||
- Variables: `dataset.select`, `dataset.next` and `dataset.prev` rewrite type and apply the other values on hidden objects and in hidden layers and groups, which stay hidden unless a visibility value shows them, so a layer shown later holds the selected data set's text; objects that are locked or in a locked layer or group are skipped, except by a visibility value.
|
||||
|
||||
Reference in New Issue
Block a user