mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 09:20:56 -06:00
Scripts: File › Scripts › Other Script… runs JavaScript files
File › Scripts › Other Script… picks a .js or .jsx file, shows a warning that can be turned off in Preferences › General, and runs the script against the open documents through the `vc` object. The new `script.run` and `script.stop` commands run and stop a script over MCP. - A run is one transaction. Each document it changed gets one undo step named Run Script. An uncaught error, a limit or a stop puts every document, the paint, the clipboard, menu state and preferences back. - A command that fails inside a run, or whose result is too large to reach the script, is undone and throws an error the script can catch. - Files a command wrote stay on disk and the report names them, also when the run is stopped from the quit question. - While a script runs, other commands that change anything fail with a busy error; MCP reports it as JSON-RPC error -32003. - In the app the run advances one slice per frame with a Stop button in the status bar. A run that adds a great many objects asks whether to continue; Stop and Escape end it. - Commands that change state outside the documents, such as prefs.*, plugin.* and file.place.queue, are refused to scripts. Docs: docs/mcp.md, docs/gaps.md, docs/roadmap.md, docs/target-app-parity.md.
This commit is contained in:
@@ -9,6 +9,9 @@ use vectorcraft_engine::Session;
|
||||
use vectorcraft_engine::cmd::clipboard::{FILE_HEAD, Flavour, PASTE_ORDER, file_flavour, is_address};
|
||||
use vectorcraft_engine::cmd::fileio;
|
||||
use vectorcraft_engine::cmd::recovery::{self, Hold, RecoveryStore};
|
||||
use vectorcraft_engine::cmd::scripts::RUN_EXTS;
|
||||
use vectorcraft_engine::script::MAX_SOURCE_BYTES;
|
||||
use vectorcraft_ui_egui::dialogs::script_run::ScriptInbox;
|
||||
use vectorcraft_ui_egui::graphics::GraphicsLoss;
|
||||
use vectorcraft_ui_egui::i18n;
|
||||
use vectorcraft_ui_egui::place::{DropTarget, PlaceArrival, PlaceInbox};
|
||||
@@ -47,6 +50,7 @@ struct Host {
|
||||
runner: eframe::WebRunner,
|
||||
inbox: Inbox,
|
||||
place_inbox: PlaceInbox,
|
||||
script_inbox: ScriptInbox,
|
||||
drag: DragPos,
|
||||
pasted: Rc<RefCell<Pasted>>,
|
||||
/// Pen and finger input read from the page's Pointer Events (`crate::pencil`).
|
||||
@@ -75,6 +79,7 @@ pub fn start() {
|
||||
runner: eframe::WebRunner::new(),
|
||||
inbox: Arc::default(),
|
||||
place_inbox: Arc::default(),
|
||||
script_inbox: Arc::default(),
|
||||
drag: DragPos::default(),
|
||||
pasted: Rc::default(),
|
||||
pointers: SharedPointers::default(),
|
||||
@@ -120,7 +125,8 @@ async fn run(host: Host, canvas: web_sys::HtmlCanvasElement, moving: Option<Movi
|
||||
}
|
||||
host.pasted.borrow_mut().ctx = Some(ctx.clone());
|
||||
let stylus = host.pointers.borrow().stylus();
|
||||
let services = services(host.inbox.clone(), host.place_inbox.clone(), ctx.clone(), host.locks.clone(), stylus);
|
||||
let services =
|
||||
services(host.inbox.clone(), host.place_inbox.clone(), host.script_inbox.clone(), ctx.clone(), host.locks.clone(), stylus);
|
||||
let app = match taken.borrow_mut().take() {
|
||||
Some(mut app) => {
|
||||
// The old services asked the old context for frames.
|
||||
@@ -425,12 +431,35 @@ impl eframe::App for WebShell {
|
||||
}
|
||||
}
|
||||
|
||||
fn services(inbox: Inbox, place_inbox: PlaceInbox, ctx: egui::Context, locks: Option<WebLocks>, stylus: StylusFeed) -> Services {
|
||||
fn services(
|
||||
inbox: Inbox,
|
||||
place_inbox: PlaceInbox,
|
||||
script_inbox: ScriptInbox,
|
||||
ctx: egui::Context,
|
||||
locks: Option<WebLocks>,
|
||||
stylus: StylusFeed,
|
||||
) -> Services {
|
||||
let open_inbox = inbox.clone();
|
||||
let picked = place_inbox.clone();
|
||||
let place_ctx = ctx.clone();
|
||||
let screen_color = eye_dropper(ctx.clone());
|
||||
let (script_picked, script_ctx) = (script_inbox.clone(), ctx.clone());
|
||||
Services {
|
||||
// File › Scripts › Other Script…: the picked file goes to the warning before it runs.
|
||||
script_async: Some(Box::new(move || {
|
||||
let inbox = script_picked.clone();
|
||||
let ctx = script_ctx.clone();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let filter = vectorcraft_ui_egui::i18n::tr_ctx(vectorcraft_ui_egui::i18n::current(), "file-filter", "Scripts");
|
||||
let Some(file) = rfd::AsyncFileDialog::new().add_filter(filter, RUN_EXTS).pick_file().await else {
|
||||
return;
|
||||
};
|
||||
let bytes = if file.inner().size() > MAX_SOURCE_BYTES as f64 { None } else { Some(file.read().await) };
|
||||
inbox.lock().unwrap_or_else(|e| e.into_inner()).push((file.file_name(), bytes));
|
||||
ctx.request_repaint();
|
||||
});
|
||||
})),
|
||||
script_inbox: Some(script_inbox),
|
||||
// File → Place…: the picked files go to the Place dialog.
|
||||
place_async: Some(Box::new(move || {
|
||||
let inbox = picked.clone();
|
||||
|
||||
Reference in New Issue
Block a user