mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-30 21:36:46 -06:00
Merge main into feat/widget-input
# Conflicts: # ROADMAP.md # crates/plugin/src/manifest.rs # crates/ui-egui/src/canvas.rs # docs/gaps.md # docs/plugins.md # docs/roadmap.md # docs/target-app-parity.md
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "export-names"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
figcraft-plugin-sdk = { workspace = true }
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"id": "org.figcraft.examples.export-names",
|
||||
"name": "Export names",
|
||||
"version": "1.0.0",
|
||||
"description": "Saves the names of the selected layers as a text file, one per line.",
|
||||
"main": "export_names.wasm",
|
||||
"api": 4,
|
||||
"permissions": {
|
||||
"access": "read",
|
||||
"commands": ["node.get"],
|
||||
"saveFile": true
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"id": "export",
|
||||
"label": "Export names…",
|
||||
"params": [
|
||||
{ "name": "children", "label": "Include layers inside", "type": "boolean", "default": true }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//! Export names: saves the names of the selected layers (optionally the layers inside them too) as
|
||||
//! the text file `layer-names.txt`, through the host's own Save dialog (`save_file`).
|
||||
|
||||
use figcraft_plugin_sdk::{Input, Result, Value, call, entry, json, notify, save_file};
|
||||
|
||||
/// Layers looked at in one run, at most.
|
||||
const MAX_LAYERS: usize = 10_000;
|
||||
|
||||
/// The names as the file's text: one per line, a line break at the end, line breaks inside a name
|
||||
/// turned into spaces.
|
||||
fn names_text(names: &[String]) -> String {
|
||||
let mut text = String::new();
|
||||
for name in names {
|
||||
text.extend(name.chars().map(|c| if c == '\n' || c == '\r' { ' ' } else { c }));
|
||||
text.push('\n');
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
fn run(input: Input) -> Result<Value> {
|
||||
if input.selection.is_empty() {
|
||||
return Err("Select the layers to export first".into());
|
||||
}
|
||||
let inside = input.bool("children").unwrap_or(true);
|
||||
// Depth first, in layer order: the stack takes the children reversed.
|
||||
let mut stack: Vec<u64> = input.selection.iter().rev().copied().collect();
|
||||
let mut names = Vec::new();
|
||||
while let Some(id) = stack.pop() {
|
||||
if names.len() >= MAX_LAYERS {
|
||||
break;
|
||||
}
|
||||
let node = call("node.get", &json!({"id": id}))?;
|
||||
names.push(node["name"].as_str().unwrap_or_default().to_string());
|
||||
if inside && let Some(children) = node["children"].as_array() {
|
||||
stack.extend(children.iter().rev().filter_map(|c| c["id"].as_u64()));
|
||||
}
|
||||
}
|
||||
save_file("layer-names.txt", "text/plain", names_text(&names).as_bytes())?;
|
||||
notify(&format!("Exporting {} names", names.len()));
|
||||
Ok(json!({"names": names.len()}))
|
||||
}
|
||||
|
||||
entry!(run);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::names_text;
|
||||
|
||||
#[test]
|
||||
fn names_become_lines() {
|
||||
let names = ["Card".to_string(), "Title\nline".to_string(), String::new()];
|
||||
assert_eq!(names_text(&names), "Card\nTitle line\n\n");
|
||||
assert_eq!(names_text(&[]), "");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user