mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-03 00:59:00 +00:00
Merge main into feat/wiggle-paths-spatial-phase
This commit is contained in:
commit
32f058ea05
627 files changed
+54573
-48511
No files matched your search
@@ -88,28 +88,7 @@ pub fn run_code(s: &mut Session, code: &str, name: &str) -> Outcome {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
#[cfg(test)]
|
||||
mod tests_callable;
|
||||
#[cfg(test)]
|
||||
mod tests_ease_presets;
|
||||
#[cfg(test)]
|
||||
mod tests_include;
|
||||
#[cfg(test)]
|
||||
mod tests_panels;
|
||||
#[cfg(test)]
|
||||
mod tests_tasks;
|
||||
#[cfg(test)]
|
||||
mod tests_ui;
|
||||
#[cfg(test)]
|
||||
mod tests_ui_more;
|
||||
|
||||
// #760: `$.evalFile` and `BridgeTalk`.
|
||||
#[cfg(test)]
|
||||
mod tests_evalfile;
|
||||
// `/browser/…` is the web editor's file table, an absolute path on wasm; on Windows it has no
|
||||
// drive, so the script runtime resolves it under the current drive, where the host holds nothing.
|
||||
#[cfg(test)]
|
||||
#[cfg(not(windows))]
|
||||
mod tests_services;
|
||||
#[cfg(test)]
|
||||
mod tests_ui_mouse;
|
||||
@@ -189,7 +189,12 @@ BridgeTalk.pump = function () { return false; };
|
||||
|
||||
// ------------------------------------------------------------------ File / Folder
|
||||
|
||||
function __join(a, b) { return a.replace(/[\/\\]+$/, "") + "/" + b; }
|
||||
function __trimPathSeparators(path) {
|
||||
var end = path.length;
|
||||
while (end > 0 && (path.charAt(end - 1) === "/" || path.charAt(end - 1) === "\\")) end--;
|
||||
return path.slice(0, end);
|
||||
}
|
||||
function __join(a, b) { return __trimPathSeparators(a) + "/" + b; }
|
||||
|
||||
function File(path) {
|
||||
if (!(this instanceof File)) return new File(path);
|
||||
@@ -292,9 +297,9 @@ Folder.prototype = {
|
||||
get fsName() { return this.__path; },
|
||||
get fullName() { return this.__path; },
|
||||
get absoluteURI() { return this.__path; },
|
||||
get name() { return this.__path.replace(/[\/\\]+$/, "").split(/[\/\\]/).pop(); },
|
||||
get name() { return __trimPathSeparators(this.__path).split(/[\/\\]/).pop(); },
|
||||
get displayName() { return this.name; },
|
||||
get path() { var p = this.__path.replace(/[\/\\]+$/, "").split(/[\/\\]/); p.pop(); return p.join("/"); },
|
||||
get path() { var p = __trimPathSeparators(this.__path).split(/[\/\\]/); p.pop(); return p.join("/"); },
|
||||
get parent() { return new Folder(this.path); },
|
||||
get exists() { return JSON.parse(__file("isDir", this.__path)) === true; },
|
||||
toString: function () { return this.__path; },
|
||||
@@ -420,7 +425,7 @@ function __callable(o) {
|
||||
delete f.length;
|
||||
Object.setPrototypeOf(f, Object.getPrototypeOf(o));
|
||||
var keys = Object.keys(o);
|
||||
for (var i = 0; i < keys.length; i++) f[keys[i]] = o[keys[i]];
|
||||
for (const elem of keys) f[elem] = o[elem];
|
||||
var self = new Proxy(f, {
|
||||
get: __attr.get,
|
||||
apply: function (t, thisArg, args) { return self.property(args[0]); },
|
||||
@@ -473,7 +478,7 @@ function MarkerValue(comment, chapter, url, frameTarget, cuePointName, params) {
|
||||
}
|
||||
MarkerValue.prototype.getParameters = function () {
|
||||
var o = {};
|
||||
for (var i = 0; i < this.__params.length; i++) o[this.__params[i][0]] = this.__params[i][1];
|
||||
for (const elem of this.__params) o[elem[0]] = elem[1];
|
||||
return o;
|
||||
};
|
||||
MarkerValue.prototype.setParameters = function (o) {
|
||||
@@ -513,8 +518,8 @@ TextDocument.prototype = {
|
||||
set text(v) { this.__text = String(v); this.__textSet = true; },
|
||||
__style: function (k, d) {
|
||||
if (k in this.__changed) return this.__changed[k];
|
||||
if (this.__base && this.__base.first && k in this.__base.first) return this.__base.first[k];
|
||||
if (this.__base && this.__base.para && k in this.__base.para) return this.__base.para[k];
|
||||
if (this.__base?.first && k in this.__base.first) return this.__base.first[k];
|
||||
if (this.__base?.para && k in this.__base.para) return this.__base.para[k];
|
||||
return d;
|
||||
},
|
||||
__set: function (k, v) { this.__changed[k] = v; },
|
||||
@@ -580,14 +585,14 @@ TextDocument.prototype = {
|
||||
},
|
||||
get tsume() { return this.__style("tsume", 0); },
|
||||
set tsume(v) { this.__set("tsume", __num(v)); },
|
||||
get boxText() { return !!(this.__base && this.__base.box_size); },
|
||||
get boxText() { return !!this.__base?.box_size; },
|
||||
get pointText() { return !this.boxText; },
|
||||
get boxTextSize() { return this.__base && this.__base.box_size ? this.__base.box_size.slice() : undefined; },
|
||||
get boxTextSize() { return this.__base?.box_size ? this.__base.box_size.slice() : undefined; },
|
||||
set boxTextSize(v) {
|
||||
var p = this.boxTextPos || [0, 0];
|
||||
this.__set("box", [p[0], p[1], v[0], v[1]]);
|
||||
},
|
||||
get boxTextPos() { return this.__base && this.__base.box_size ? this.__base.box_pos.slice() : undefined; },
|
||||
get boxTextPos() { return this.__base?.box_size ? this.__base.box_pos.slice() : undefined; },
|
||||
get startIndent() { return this.__style("indentLeft", 0); },
|
||||
set startIndent(v) { this.__set("indentLeft", __num(v)); },
|
||||
get endIndent() { return this.__style("indentRight", 0); },
|
||||
@@ -665,7 +670,7 @@ PropertyBase.prototype = {
|
||||
get isMask() { return !!this.__info().isMask; },
|
||||
get selected() {
|
||||
var c = __get("item", { id: this.__layer.__comp }).selectedProperties;
|
||||
for (var i = 0; i < c.length; i++) if (c[i][0] === this.__layer.__id && c[i][1] === this.__uid) return true;
|
||||
for (const elem of c) if (elem[0] === this.__layer.__id && elem[1] === this.__uid) return true;
|
||||
return false;
|
||||
},
|
||||
set selected(v) {
|
||||
@@ -842,7 +847,7 @@ Property.prototype.property = function () { return null; };
|
||||
i = __num(i, "keyIndex");
|
||||
// Only the key asked for: fetching the whole list made every key call linear (#814).
|
||||
var r = i >= 1 && Math.floor(i) === i ? __get("keys", { comp: this.__layer.__comp, layer: this.__layer.__id, uid: this.__uid, index: i - 1 }) : null;
|
||||
if (!r || !r.key) throw __err("keyIndex " + i + " is out of range 1.." + (r ? r.count : this.numKeys));
|
||||
if (!r?.key) throw __err("keyIndex " + i + " is out of range 1.." + (r ? r.count : this.numKeys));
|
||||
return r.key;
|
||||
};
|
||||
// Script value → engine JSON for `prop.set` / `prop.addKey`.
|
||||
@@ -853,13 +858,13 @@ Property.prototype.property = function () { return null; };
|
||||
return __get("textdoc", { comp: this.__layer.__comp, layer: this.__layer.__id, uid: this.__uid, time: time, attrs: td.__attrs() });
|
||||
}
|
||||
if (t === "SHAPE") {
|
||||
if (!v || !v.vertices) throw __err("value must be a Shape");
|
||||
if (!v?.vertices) throw __err("value must be a Shape");
|
||||
var n = v.vertices.length;
|
||||
function fit(a) { var o = []; for (var i = 0; i < n; i++) o.push(a && a[i] ? [a[i][0], a[i][1]] : [0, 0]); return o; }
|
||||
function fit(a) { var o = []; for (var i = 0; i < n; i++) o.push(a?.[i] ? [a[i][0], a[i][1]] : [0, 0]); return o; }
|
||||
return { vertices: fit(v.vertices), in_tangents: fit(v.inTangents), out_tangents: fit(v.outTangents), closed: v.closed !== false };
|
||||
}
|
||||
if (t === "LAYER_INDEX") {
|
||||
var idx = v && v.__id !== undefined ? v.index : __num(v);
|
||||
var idx = v?.__id !== undefined ? v.index : __num(v);
|
||||
if (!idx) return null;
|
||||
return this.__layer.containingComp.layer(idx).__id;
|
||||
}
|
||||
@@ -1177,7 +1182,7 @@ Layer.prototype = {
|
||||
},
|
||||
get selectedProperties() {
|
||||
var c = this.containingComp.__info().selectedProperties, out = [];
|
||||
for (var i = 0; i < c.length; i++) if (c[i][0] === this.__id) out.push(__propOf(this, __get("node", { comp: this.__comp, layer: this.__id, uid: c[i][1] })));
|
||||
for (const elem of c) if (elem[0] === this.__id) out.push(__propOf(this, __get("node", { comp: this.__comp, layer: this.__id, uid: elem[1] })));
|
||||
return out;
|
||||
},
|
||||
get parent() { var p = this.__info().parent; return p === null ? null : __layer(this.__comp, p); },
|
||||
@@ -1205,7 +1210,7 @@ Layer.prototype = {
|
||||
var before = this.containingComp.__info().layers;
|
||||
__call("edit.duplicate", this.__p());
|
||||
var after = this.containingComp.__info().layers;
|
||||
for (var i = 0; i < after.length; i++) if (before.indexOf(after[i]) < 0) return __layer(this.__comp, after[i]);
|
||||
for (const elem of after) if (before.indexOf(elem) < 0) return __layer(this.__comp, elem);
|
||||
return null;
|
||||
},
|
||||
copyToComp: function (comp) {
|
||||
@@ -1310,7 +1315,7 @@ AVLayer.prototype.constructor = AVLayer;
|
||||
// AVLayer.replaceSource). `fixExpressions` is accepted for compatibility: only AV sources are
|
||||
// replaced, so the property tree and the layer's name stay and expressions keep resolving.
|
||||
P.replaceSource = function (newSource, fixExpressions) {
|
||||
if (!newSource || newSource.__id === undefined) throw __err("replaceSource needs a project item");
|
||||
if (newSource?.__id === undefined) throw __err("replaceSource needs a project item");
|
||||
__call("layer.replaceSource", { comp: this.__comp, layers: [this.__id], item: newSource.__id, fixExpressions: !!fixExpressions });
|
||||
return null;
|
||||
};
|
||||
@@ -1371,7 +1376,7 @@ LayerCollection.prototype = {
|
||||
return layer;
|
||||
},
|
||||
add: function (item, duration) {
|
||||
if (!item || item.id === undefined) throw __err("add(): expected an item");
|
||||
if (item?.id === undefined) throw __err("add(): expected an item");
|
||||
var l = this.__new(__call("layer.addItem", { comp: this.__comp, item: item.id, time: 0 }));
|
||||
return this.__dur(l, duration);
|
||||
},
|
||||
@@ -1414,15 +1419,15 @@ LayerCollection.prototype = {
|
||||
},
|
||||
precompose: function (indices, name, moveAllAttributes) {
|
||||
var ids = this.__ids(), sel = [];
|
||||
for (var i = 0; i < indices.length; i++) sel.push(ids[indices[i] - 1]);
|
||||
for (const elem of indices) sel.push(ids[elem - 1]);
|
||||
var r = __call("layer.precompose", { comp: this.__comp, layers: sel, name: String(name), mode: moveAllAttributes === false ? "leave" : "move" });
|
||||
return __item(r.comp);
|
||||
},
|
||||
byName: function (name) {
|
||||
var ids = this.__ids();
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var l = __get("layer", { comp: this.__comp, layer: ids[i] });
|
||||
if (l.name === name) return __layer(this.__comp, ids[i]);
|
||||
for (const elem of ids) {
|
||||
var l = __get("layer", { comp: this.__comp, layer: elem });
|
||||
if (l.name === name) return __layer(this.__comp, elem);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
@@ -1493,7 +1498,7 @@ Item.prototype = {
|
||||
get label() { return this.__info().label; },
|
||||
set label(v) { __call("project.setLabel", { items: [this.__id], label: __num(v, "label") }); },
|
||||
get parentFolder() { return __item(this.__info().parent); },
|
||||
set parentFolder(f) { __call("project.move", { items: [this.__id], folder: f && f.__id ? f.__id : null }); },
|
||||
set parentFolder(f) { __call("project.move", { items: [this.__id], folder: f?.__id ? f.__id : null }); },
|
||||
get selected() { return this.__info().selected; },
|
||||
set selected(v) {
|
||||
var selection = __get("project").selection;
|
||||
@@ -1527,7 +1532,7 @@ AVItem.prototype = Object.create(Item.prototype);
|
||||
def("hasVideo", function () { return this.__info().hasVideo; });
|
||||
def("hasAudio", function () { return this.__info().hasAudio; });
|
||||
def("footageMissing", function () { return !!this.__info().footageMissing; });
|
||||
def("useProxy", function () { var p = this.__info().proxy; return !!(p && p.enabled); }, function (v) {
|
||||
def("useProxy", function () { var p = this.__info().proxy; return !!p?.enabled; }, function (v) {
|
||||
if (!this.__info().proxy) throw __err("\"" + this.name + "\" has no proxy");
|
||||
__call("file.useProxy", { item: this.__id, on: !!v });
|
||||
});
|
||||
@@ -1564,9 +1569,9 @@ CompItem.prototype.constructor = CompItem;
|
||||
});
|
||||
def("activeCamera", function () {
|
||||
var ids = this.__info().layers;
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var l = __get("layer", { comp: this.__id, layer: ids[i] });
|
||||
if (l.kind === "camera" && l.enabled && l.active) return __layer(this.__id, ids[i]);
|
||||
for (const elem of ids) {
|
||||
var l = __get("layer", { comp: this.__id, layer: elem });
|
||||
if (l.kind === "camera" && l.enabled && l.active) return __layer(this.__id, elem);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
@@ -1606,7 +1611,7 @@ CompItem.prototype.constructor = CompItem;
|
||||
if (a < 1 || a > ids.length || Math.floor(a) !== a) throw __err("layer index " + a + " is out of range 1.." + ids.length);
|
||||
return __layer(this.__id, ids[a - 1]);
|
||||
}
|
||||
if (a && a.__id !== undefined && typeof b === "number") {
|
||||
if (a?.__id !== undefined && typeof b === "number") {
|
||||
var j = a.index + b;
|
||||
return j >= 1 && j <= ids.length ? __layer(this.__id, ids[j - 1]) : null;
|
||||
}
|
||||
@@ -1619,9 +1624,9 @@ CompItem.prototype.constructor = CompItem;
|
||||
P.__controllers = function () {
|
||||
var out = [];
|
||||
(function walk(list) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
if (list[i].kind === "property" || list[i].kind === "mirror" || list[i].kind === "media") out.push(list[i]);
|
||||
if (list[i].children) walk(list[i].children);
|
||||
for (const elem of list) {
|
||||
if (elem.kind === "property" || elem.kind === "mirror" || elem.kind === "media") out.push(elem);
|
||||
if (elem.children) walk(elem.children);
|
||||
}
|
||||
})(this.__eg().controls);
|
||||
return out;
|
||||
@@ -1732,7 +1737,7 @@ FolderItem.prototype.constructor = FolderItem;
|
||||
Object.defineProperty(P, "numItems", { get: function () { return new ItemCollection(this.__id).length; } });
|
||||
Object.defineProperty(P, "parentFolder", {
|
||||
get: function () { return this.__id === 0 ? null : __item(this.__info().parent); },
|
||||
set: function (f) { __call("project.move", { items: [this.__id], folder: f && f.__id ? f.__id : null }); },
|
||||
set: function (f) { __call("project.move", { items: [this.__id], folder: f?.__id ? f.__id : null }); },
|
||||
});
|
||||
P.item = function (i) {
|
||||
var v = new ItemCollection(this.__id).__at(__num(i, "index"));
|
||||
@@ -1776,7 +1781,7 @@ RenderQueueItem.prototype = {
|
||||
constructor: RenderQueueItem,
|
||||
__info: function () {
|
||||
var l = __call("renderQueue.list", {}).items;
|
||||
for (var i = 0; i < l.length; i++) if (l[i].id === this.__id) return l[i];
|
||||
for (const elem of l) if (elem.id === this.__id) return elem;
|
||||
throw __err("the render queue item no longer exists");
|
||||
},
|
||||
get comp() { return __item(this.__info().comp); },
|
||||
@@ -1812,7 +1817,7 @@ RenderQueueItem.prototype = {
|
||||
var before = __call("renderQueue.list", {}).items.map(function (i) { return i.id; });
|
||||
__call("renderQueue.duplicate", { item: this.__id });
|
||||
var after = __call("renderQueue.list", {}).items;
|
||||
for (var i = 0; i < after.length; i++) if (before.indexOf(after[i].id) < 0) return new RenderQueueItem(after[i].id);
|
||||
for (const elem of after) if (before.indexOf(elem.id) < 0) return new RenderQueueItem(elem.id);
|
||||
return null;
|
||||
},
|
||||
applyTemplate: function () {},
|
||||
@@ -1945,9 +1950,9 @@ Project.prototype = {
|
||||
}
|
||||
}
|
||||
var r = __call("file.import", args);
|
||||
if (r.errors && r.errors.length) throw __err(r.errors.join("; "));
|
||||
if (r.errors?.length) throw __err(r.errors.join("; "));
|
||||
// Composition imports create several footage items, but callers need the comp.
|
||||
return __item(r.comps && r.comps.length ? r.comps[0] : r.items[0]);
|
||||
return __item(r.comps?.length ? r.comps[0] : r.items[0]);
|
||||
},
|
||||
importFileWithDialog: function () { return null; },
|
||||
importPlaceholder: function (name, w, h, rate, dur) {
|
||||
|
||||
@@ -710,7 +710,7 @@ pub(crate) fn outcome(output: Vec<String>, result: Result<J, ScriptError>) -> Ou
|
||||
/// don't block.
|
||||
///
|
||||
/// ExtendScript's `#include` / `#includepath` are inlined first, and errors point at the file
|
||||
/// and line they came from ([`crate::include`]).
|
||||
/// and line they came from (`crate::include`).
|
||||
pub fn run(session: &mut Session, req: &ScriptRequest) -> Outcome {
|
||||
let expanded = match crate::include::expand(session, req.code, req.name) {
|
||||
Ok(x) => x,
|
||||
|
||||
@@ -213,7 +213,7 @@ function __uiResPlain(v) {
|
||||
if (v instanceof Array) return v.map(__uiResPlain);
|
||||
if (v && typeof v === "object" && v.__object) {
|
||||
var o = {};
|
||||
for (var k = 0; k < v.__object.length; k++) o[v.__object[k][0]] = __uiResPlain(v.__object[k][1]);
|
||||
for (const elem of v.__object) o[elem[0]] = __uiResPlain(elem[1]);
|
||||
return o;
|
||||
}
|
||||
return v;
|
||||
@@ -222,8 +222,8 @@ function __uiResPlain(v) {
|
||||
// A resource's own properties and its child elements (in source order).
|
||||
function __uiResProps(res) {
|
||||
var props = {}, kids = [];
|
||||
for (var k = 0; k < res.list.length; k++) {
|
||||
var e = res.list[k];
|
||||
for (const elem2 of res.list) {
|
||||
var e = elem2;
|
||||
if (e[1] && typeof e[1] === "object" && e[1].__element) kids.push([e[0], { type: e[1].__element, list: e[1].list }]);
|
||||
else props[e[0]] = __uiResPlain(e[1]);
|
||||
}
|
||||
@@ -239,17 +239,17 @@ function __uiApplyRes(c, res) {
|
||||
// Ranges before values, items before selections.
|
||||
if (props.minvalue !== undefined) c.minvalue = __num(props.minvalue);
|
||||
if (props.maxvalue !== undefined) c.maxvalue = __num(props.maxvalue);
|
||||
if (props.items instanceof Array && c.items) for (var n = 0; n < props.items.length; n++) c.add("item", props.items[n]);
|
||||
if (props.items instanceof Array && c.items) for (const elem of props.items) c.add("item", elem);
|
||||
for (var k in props) {
|
||||
if (__uiResSkip[k]) continue;
|
||||
var v = props[k];
|
||||
if (__uiSizes[k] && v instanceof Array) v = __dim(v[0], v[1]);
|
||||
c[k] = v;
|
||||
}
|
||||
for (var j = 0; j < pk.kids.length; j++) {
|
||||
var child = __uiAddRes(c, pk.kids[j][1]);
|
||||
child.__key = pk.kids[j][0];
|
||||
c[pk.kids[j][0]] = child;
|
||||
for (const elem of pk.kids) {
|
||||
var child = __uiAddRes(c, elem[1]);
|
||||
child.__key = elem[0];
|
||||
c[elem[0]] = child;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -268,7 +268,7 @@ Window.alert = function (s) { alert(s); };
|
||||
Window.confirm = function (s) { return confirm(s); };
|
||||
Window.prompt = function (m, d) { return prompt(m, d); };
|
||||
Window.find = function (type, title) {
|
||||
for (var i = 0; i < __uiWins.length; i++) if (__uiWins[i].text === title) return __uiWins[i];
|
||||
for (const elem of __uiWins) if (elem.text === title) return elem;
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -335,7 +335,7 @@ function __uiColor(c) {
|
||||
return null;
|
||||
}
|
||||
function __uiFontSize(f, g) {
|
||||
var x = f && f.size !== undefined ? Number(f.size) : g && g.font && g.font.size !== undefined ? Number(g.font.size) : 12;
|
||||
var x = f?.size !== undefined ? Number(f.size) : g?.font && g.font.size !== undefined ? Number(g.font.size) : 12;
|
||||
return x > 0 ? x : 12;
|
||||
}
|
||||
|
||||
@@ -371,11 +371,11 @@ function __uiGraphics() {
|
||||
this.__push({ op: "fill", color: brush ? brush.color : null, path: (path || this.currentPath).slice() });
|
||||
},
|
||||
strokePath: function (pen, path) {
|
||||
this.__push({ op: "stroke", color: pen ? pen.color : null, width: pen && pen.lineWidth !== undefined ? pen.lineWidth : 1, path: (path || this.currentPath).slice() });
|
||||
this.__push({ op: "stroke", color: pen ? pen.color : null, width: pen?.lineWidth !== undefined ? pen.lineWidth : 1, path: (path || this.currentPath).slice() });
|
||||
},
|
||||
drawString: function (text, pen, x, y, font) {
|
||||
var f = font || this.font;
|
||||
this.__push({ op: "text", text: String(text), color: pen ? pen.color : null, x: __num(x || 0), y: __num(y || 0), size: __uiFontSize(f, this), style: String(f && f.style !== undefined ? f.style : "") });
|
||||
this.__push({ op: "text", text: String(text), color: pen ? pen.color : null, x: __num(x || 0), y: __num(y || 0), size: __uiFontSize(f, this), style: String(f?.style !== undefined ? f.style : "") });
|
||||
},
|
||||
drawOSControl: function () { this.__push({ op: "os" }); },
|
||||
drawImage: function (img, x, y, w, h) { this.__push({ op: "image", image: __uiImageJson(img), x: __num(x || 0), y: __num(y || 0), w: w === undefined ? 0 : __num(w), h: h === undefined ? 0 : __num(h) }); },
|
||||
@@ -384,7 +384,7 @@ function __uiGraphics() {
|
||||
var size = __uiFontSize(font, this);
|
||||
var lines = String(s).split("\n");
|
||||
var cw = size * 0.58, w = 0;
|
||||
for (var i = 0; i < lines.length; i++) w = Math.max(w, Math.ceil(lines[i].length * cw));
|
||||
for (const elem of lines) w = Math.max(w, Math.ceil(elem.length * cw));
|
||||
var n = lines.length;
|
||||
if (boundaryWidth > 0 && w > boundaryWidth) {
|
||||
n = Math.ceil(w / boundaryWidth) + lines.length - 1;
|
||||
@@ -405,14 +405,14 @@ function __uiRunDraws(win) {
|
||||
})(win);
|
||||
if (!drawn.length) return;
|
||||
__uiLayout(win);
|
||||
for (var k = 0; k < drawn.length; k++) {
|
||||
var c = drawn[k];
|
||||
for (const elem of drawn) {
|
||||
var c = elem;
|
||||
c.__draw = [];
|
||||
c.graphics.__rec = c.__draw;
|
||||
try {
|
||||
c.onDraw.call(c, { type: "draw", target: c });
|
||||
} catch (e) {
|
||||
try { writeLn("onDraw: " + (e && e.message ? e.message : e)); } catch (e2) {}
|
||||
try { writeLn("onDraw: " + (e?.message ? e.message : e)); } catch (e2) {}
|
||||
}
|
||||
c.graphics.__rec = null;
|
||||
}
|
||||
@@ -498,7 +498,7 @@ function __uiProto() {
|
||||
var arg = t === "image" || t === "iconbutton" ? text : typeof text === "object" && text !== null && !(text instanceof Array) ? "" : text;
|
||||
__uiInit(c, t, this.window, this, bounds, arg, props);
|
||||
if ((t === "dropdownlist" || t === "listbox") && text instanceof Array) {
|
||||
for (var i = 0; i < text.length; i++) c.add("item", text[i]);
|
||||
for (const elem of text) c.add("item", elem);
|
||||
c.text = "";
|
||||
}
|
||||
if (t === "slider" || t === "scrollbar") {
|
||||
@@ -521,7 +521,7 @@ function __uiProto() {
|
||||
if (t === "checkbox" || t === "radiobutton") c.value = false;
|
||||
if (t === "edittext" && c.properties.multiline) c.__multiline = true;
|
||||
if (t === "edittext" && c.properties.readonly) c.__readonly = true;
|
||||
if (c.properties && c.properties.name) c.name = c.properties.name;
|
||||
if (c.properties?.name) c.name = c.properties.name;
|
||||
this.children.push(c);
|
||||
if (t === "tab" && this.type === "tabbedpanel" && this.__sel.length === 0) this.__sel = [this.children.length - 1];
|
||||
return c;
|
||||
@@ -608,21 +608,21 @@ function __uiProto() {
|
||||
removeEventListener: function (name, fn, capture) {
|
||||
var k = String(name).toLowerCase();
|
||||
var m = capture ? this.__capture : this.__listeners;
|
||||
if (m && m[k]) m[k] = m[k].filter(function (f) { return f !== fn; });
|
||||
if (m?.[k]) m[k] = m[k].filter(function (f) { return f !== fn; });
|
||||
},
|
||||
dispatchEvent: function (e) { this.__fire("on" + String(e.type || e).charAt(0).toUpperCase() + String(e.type || e).slice(1)); },
|
||||
__fire: function (name) {
|
||||
var r;
|
||||
if (typeof this[name] === "function") r = this[name].call(this);
|
||||
var l = this.__listeners[name.slice(2).toLowerCase()];
|
||||
if (l) for (var i = 0; i < l.length; i++) l[i].call(this, { type: name.slice(2).toLowerCase(), target: this });
|
||||
if (l) for (const elem of l) elem.call(this, { type: name.slice(2).toLowerCase(), target: this });
|
||||
return r;
|
||||
},
|
||||
__handlers: function () {
|
||||
var out = [];
|
||||
var names = ["onClick", "onChange", "onChanging", "onDoubleClick", "onClose", "onShow", "onActivate", "onDeactivate", "onResize", "onDraw"];
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var n = names[i];
|
||||
for (const elem of names) {
|
||||
var n = elem;
|
||||
if (typeof this[n] === "function" || (this.__listeners[n.slice(2).toLowerCase()] || []).length) out.push(n);
|
||||
}
|
||||
return out;
|
||||
@@ -634,8 +634,8 @@ function __uiProto() {
|
||||
(function () {
|
||||
var all = ["Window", "Panel", "Group", "Button", "IconButton", "StaticText", "EditText", "Checkbox", "RadioButton", "Slider",
|
||||
"Scrollbar", "Progressbar", "DropDownList", "ListBox", "TabbedPanel", "Tab", "ScriptUIImage"];
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
var C = __ecGlobal[all[i]];
|
||||
for (const elem of all) {
|
||||
var C = __ecGlobal[elem];
|
||||
var p = __uiProto();
|
||||
for (var k in p) C.prototype[k] = p[k];
|
||||
Object.defineProperty(C.prototype, "bounds", {
|
||||
@@ -737,8 +737,8 @@ function __uiMouseListeners(c, type, capture) {
|
||||
// The mouse events that reach a listener at `c`: its own, and its containers' (`inherited`).
|
||||
function __uiMouseWanted(c, inherited) {
|
||||
var out = inherited.slice();
|
||||
for (var i = 0; i < __uiMouseKinds.length; i++) {
|
||||
var t = __uiMouseKinds[i];
|
||||
for (const elem of __uiMouseKinds) {
|
||||
var t = elem;
|
||||
if (out.indexOf(t) < 0 && (__uiMouseListeners(c, t, false).length || __uiMouseListeners(c, t, true).length)) out.push(t);
|
||||
}
|
||||
return out;
|
||||
@@ -793,7 +793,7 @@ function __uiMouseFire(target, type, v) {
|
||||
var l = __uiMouseListeners(c, type, capture);
|
||||
e.currentTarget = c;
|
||||
e.eventPhase = phase;
|
||||
for (var i = 0; i < l.length; i++) if (typeof l[i] === "function") l[i].call(c, e);
|
||||
for (const elem of l) if (typeof elem === "function") elem.call(c, e);
|
||||
};
|
||||
var n = path.length - 1;
|
||||
for (var i = 0; i < n; i++) {
|
||||
@@ -832,7 +832,7 @@ function __uiJson(c, inherited) {
|
||||
if (m) o.margins = m;
|
||||
if (typeof c.spacing === "number") o.spacing = c.spacing;
|
||||
var kids = c.children;
|
||||
for (var i = 0; i < kids.length; i++) o.children.push(__uiJson(kids[i], mouse));
|
||||
for (const elem of kids) o.children.push(__uiJson(elem, mouse));
|
||||
}
|
||||
if (c.alignment !== null && c.alignment !== undefined) o.alignment = __uiAlign(c.alignment);
|
||||
if (c.type === "checkbox" || c.type === "radiobutton") o.checked = !!c.value;
|
||||
@@ -859,8 +859,8 @@ function __uiJson(c, inherited) {
|
||||
// The open windows of this script, for the host.
|
||||
function __uiSnapshot() {
|
||||
var out = [];
|
||||
for (var i = 0; i < __uiWins.length; i++) {
|
||||
var w = __uiWins[i];
|
||||
for (const elem of __uiWins) {
|
||||
var w = elem;
|
||||
if (w.__shown && !w.__closed) __uiRunDraws(w);
|
||||
out.push({ id: w.__wid, kind: w.__kind, title: String(w.text || ""), visible: !!w.__shown && !w.__closed, modal: !!w.__modal, root: __uiJson(w) });
|
||||
}
|
||||
@@ -871,12 +871,12 @@ function __uiSnapshot() {
|
||||
|
||||
function __uiOpenCount() {
|
||||
var n = 0;
|
||||
for (var i = 0; i < __uiWins.length; i++) if (__uiWins[i].__shown && !__uiWins[i].__closed) n++;
|
||||
for (const elem of __uiWins) if (elem.__shown && !elem.__closed) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
function __uiWin(id) {
|
||||
for (var i = 0; i < __uiWins.length; i++) if (__uiWins[i].__wid === id) return __uiWins[i];
|
||||
for (const elem of __uiWins) if (elem.__wid === id) return elem;
|
||||
return null;
|
||||
}
|
||||
function __uiFind(c, id) {
|
||||
@@ -914,7 +914,7 @@ function __uiDispatch(winId, id, kind, valueJson) {
|
||||
if (!c || c.enabled === false) return;
|
||||
var radio = function () {
|
||||
var sib = c.parent ? c.parent.children : [];
|
||||
for (var i = 0; i < sib.length; i++) if (sib[i] !== c && sib[i].type === "radiobutton") sib[i].value = false;
|
||||
for (const elem of sib) if (elem !== c && elem.type === "radiobutton") elem.value = false;
|
||||
c.value = true;
|
||||
};
|
||||
// A mouse click carries the pointer's details ({x, y, button, detail, …keys}).
|
||||
|
||||
@@ -55,7 +55,7 @@ mod imp {
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::*;
|
||||
use super::{J, err, json};
|
||||
|
||||
enum Sock {
|
||||
Stream { s: TcpStream, buf: Vec<u8>, eof: bool },
|
||||
|
||||
@@ -10,6 +10,18 @@ use serde_json::{Value, json};
|
||||
|
||||
use crate::{Outcome, run_code};
|
||||
|
||||
mod callable;
|
||||
mod ease_presets;
|
||||
mod evalfile;
|
||||
mod include;
|
||||
mod panels;
|
||||
#[cfg(not(windows))]
|
||||
mod services;
|
||||
mod tasks;
|
||||
mod ui;
|
||||
mod ui_more;
|
||||
mod ui_mouse;
|
||||
|
||||
fn session() -> Session {
|
||||
let mut s = Session { expr: Some(Arc::new(effectcraft_expr::Expressions)), expr_check: Some(effectcraft_expr::check_syntax), ..Default::default() };
|
||||
crate::install(&mut s);
|
||||
@@ -482,6 +494,38 @@ fn layers_items_and_markers() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_paths_trim_trailing_separators() {
|
||||
let mut s = session();
|
||||
let o = ok(
|
||||
&mut s,
|
||||
r#"
|
||||
var paths = ['', '/', '///', 'a/b/', 'a\\b\\/', 'a/b', 'a/\n'];
|
||||
var result = paths.map(function (path) {
|
||||
var folder = new Folder(path);
|
||||
return [folder.name, folder.path, __join(path, 'child')];
|
||||
});
|
||||
// A long run followed by a non-separator used to cause quadratic regex backtracking.
|
||||
var longPath = Array(20001).join('/') + 'x';
|
||||
result.push([new Folder(longPath).name, new Folder(longPath).path.length, __join(longPath, 'child').length]);
|
||||
result
|
||||
"#,
|
||||
);
|
||||
assert_eq!(
|
||||
o.result,
|
||||
json!([
|
||||
["", "", "/child"],
|
||||
["", "", "/child"],
|
||||
["", "", "/child"],
|
||||
["b", "a", "a/b/child"],
|
||||
["b", "a", "a\\b/child"],
|
||||
["b", "a", "a/b/child"],
|
||||
["\n", "a", "a/\n/child"],
|
||||
["x", 19999, 20007]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn security_gate_for_files() {
|
||||
let dir = std::env::temp_dir().join(format!("ec-script-gate-{}", std::process::id()));
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user