From be2c73ece94934593bd87dad0fad4d3648ff3fe1 Mon Sep 17 00:00:00 2001 From: Nigel Ihza Firdausy <106657905+Ni-zav@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:25:13 +0700 Subject: [PATCH] feat(script): remove a range of keyframes in one command (#814) --- crates/script/src/prelude.js | 13 +++++++++++++ crates/script/src/tests.rs | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/crates/script/src/prelude.js b/crates/script/src/prelude.js index f50a968c..973d7676 100644 --- a/crates/script/src/prelude.js +++ b/crates/script/src/prelude.js @@ -843,6 +843,19 @@ Property.prototype.property = function () { return null; }; return __call(id, params); }; P.removeKey = function (i) { this.__keyCmd(i, "keys.delete"); }; + // EffectCraft extension: remove an inclusive range with one engine edit instead of + // cloning the project for every removed key. Indices are one-based as in removeKey. + P.removeKeys = function (first, last) { + var keys = this.__keys(); + first = __num(first, "first key"); + last = __num(last, "last key"); + if (!Number.isInteger(first) || !Number.isInteger(last) || first < 1 || last < first || last > keys.length) + throw __err("removeKeys: key range is out of bounds"); + var targets = []; + for (var i = first; i <= last; i++) + targets.push({ layer: this.__layer.__id, prop: this.__uid, index: i - 1 }); + return __call("keys.delete", { comp: this.__layer.__comp, __keys: targets }); + }; P.keyInInterpolationType = function (i) { return KeyframeInterpolationType[this.__key(i).inInterp]; }; P.keyOutInterpolationType = function (i) { return KeyframeInterpolationType[this.__key(i).outInterp]; }; // Values that don't interpolate (checkboxes, popups, text) only hold. diff --git a/crates/script/src/tests.rs b/crates/script/src/tests.rs index b50a7bfb..92519491 100644 --- a/crates/script/src/tests.rs +++ b/crates/script/src/tests.rs @@ -730,3 +730,23 @@ fn import_options_sequence_picks_one_still_or_the_run() { assert_eq!(kind_of(&s, "shot_[0001-0003].png"), FootageKind::Sequence); let _ = std::fs::remove_dir_all(&dir); } + +#[test] +fn script_remove_keys_batches_a_range_as_one_edit() { + let mut s = session(); + let o = ok( + &mut s, + "var c = app.project.items.addComp('Batch Keys', 100, 100, 1, 10, 25); var p = c.layers.addNull().transform.rotation; for (var i = 0; i < 5; i++) p.setValueAtTime(i, i * 10); p.removeKeys(2, 4); [p.numKeys, p.keyTime(1), p.keyTime(2)]", + ); + assert_eq!(o.result, json!([2, 0, 4])); + // The entire deletion is one undo step; keys outside the range survive. + s.undo(); + let restored = ok(&mut s, "app.project.item(1).layer(1).transform.rotation.numKeys"); + assert_eq!(restored.result, json!(5)); + let invalid = run_code( + &mut s, + "app.project.item(1).layer(1).transform.rotation.removeKeys(0, 2)", + "remove-keys-range.jsx", + ); + assert!(invalid.error.is_some()); +}