diff --git a/src/004-base/proxies.js b/src/004-base/proxies.js index 0b39cd557d8bc07c7f9760ab3fb070c1c9904abe..03fc68bf390c917ca8c95735fa47a3463e24287a 100644 --- a/src/004-base/proxies.js +++ b/src/004-base/proxies.js @@ -1,96 +1,102 @@ -window.createReadonlyProxy = function(target) { - if (target == null) return target; //intentionally == - if (target.__isReadonlyProxy) { return target; } - if (_.isArray(target)) { - return new Proxy(target, { - get:function(o, prop) { - const val = o[prop]; - if (typeof val === 'function') { - if (['push', 'unshift', 'pop'].includes(prop)) { - return function () { - throw Error("Cannot modify a readonly array"); +(function() { + const readOnlySymbol = Symbol("readonly proxy"); + window.createReadonlyProxy = function(target) { + if (target == null) return target; //intentionally == + if (target[readOnlySymbol]) { return target; } + if (_.isArray(target)) { + return new Proxy(target, { + get:function(o, prop) { + if(prop === readOnlySymbol) { return true; } + const val = o[prop]; + if (typeof val === 'function') { + if (['push', 'unshift', 'pop'].includes(prop)) { + return function () { + throw Error("Cannot modify a readonly array"); + } } + return val.bind(target); } - return val.bind(target); + return createReadonlyProxy(val); + }, + set:function(o, prop, value) { + return true; + }, + deleteProperty:function(o, prop) { + return true; } - return createReadonlyProxy(val); - }, - set:function(o, prop, value) { - return true; - }, - deleteProperty:function(o, prop) { - return true; - } - }); - } - if (_.isObject(target)) { - return new Proxy(target, { - get:function(o, prop) { - if(prop == '__isReadonlyProxy') { return true; } - return createReadonlyProxy(o[prop]); - }, - set:function(o, prop, value) { - return true; - }, - deleteProperty:function(o, prop) { - return true; - } - }); - } - return target; -}; -window.createCheatProxy = function(target) { - if (target == null) return target; //intentionally == - if (target.__isCheatProxy) { return target; } - if (_.isArray(target)) { - return new Proxy(target, { - get:function(o, prop) { - const val = o[prop]; - if (typeof val === 'function') { - if (['push', 'unshift', 'pop'].includes(prop)) { - return function (el) { - const retval = Array.prototype[prop].apply(o, arguments); - //Make sure we set cheater after calling the function - State.variables.cheater = 1; - return retval; + }); + } + if (_.isObject(target)) { + return new Proxy(target, { + get:function(o, prop) { + if(prop === readOnlySymbol) { return true; } + return createReadonlyProxy(o[prop]); + }, + set:function(o, prop, value) { + return true; + }, + deleteProperty:function(o, prop) { + return true; + } + }); + } + return target; + }; + const cheaterSymbol = Symbol("cheating proxy"); + window.createCheatProxy = function(target) { + if (target == null) return target; //intentionally == + if (target[cheaterSymbol]) { return target; } + if (_.isArray(target)) { + return new Proxy(target, { + get:function(o, prop) { + if(prop === cheaterSymbol) { return true; } + const val = o[prop]; + if (typeof val === 'function') { + if (['push', 'unshift', 'pop'].includes(prop)) { + return function (el) { + const retval = Array.prototype[prop].apply(o, arguments); + //Make sure we set cheater after calling the function + State.variables.cheater = 1;//Can't use `V` because it probably points to a proxy. + return retval; + } } + return val.bind(target); } - return val.bind(target); + return createCheatProxy(val); + }, + set:function(o, prop, value) { + o[prop] = value; + State.variables.cheater = 1;//Can't use `V` because it probably points to a proxy. + return true; + }, + deleteProperty:function(o, prop) { + delete o[prop]; + State.variables.cheater = 1;//Can't use `V` because it probably points to a proxy. + return true; } - return createCheatProxy(val); - }, - set:function(o, prop, value) { - o[prop] = value; - State.variables.cheater = 1; - return true; - }, - deleteProperty:function(o, prop) { - delete o[prop]; - State.variables.cheater = 1; - return true; - } - }); - } - if (_.isObject(target)) { - return new Proxy(target, { - get:function(o, prop) { - if(prop == '__isCheatProxy') { return true; } - return createCheatProxy(o[prop]); - }, - set:function(o, prop, value) { - o[prop] = value; - State.variables.cheater = 1; - return true; - }, - deleteProperty:function(o, prop) { - delete o[prop]; - State.variables.cheater = 1; - return true; - } - }); - } - return target; -}; + }); + } + if (_.isObject(target)) { + return new Proxy(target, { + get:function(o, prop) { + if(prop === cheaterSymbol) { return true; } + return createCheatProxy(o[prop]); + }, + set:function(o, prop, value) { + o[prop] = value; + State.variables.cheater = 1; + return true; + }, + deleteProperty:function(o, prop) { + delete o[prop]; + State.variables.cheater = 1; + return true; + } + }); + } + return target; + }; +})(); Object.defineProperty(window, "V", { get: function() { if (window.storyProxy != null) { return window.storyProxy; } diff --git a/src/js/DefaultRules.js b/src/js/DefaultRules.js index a1cc473e971efd4e2c8546cecc75bb0cc84350ad..cd5fd6efe7a120ac3f2aa55da62c1579d685e8b8 100644 --- a/src/js/DefaultRules.js +++ b/src/js/DefaultRules.js @@ -2937,9 +2937,20 @@ window.DefaultRules = (function() { } } - let rxCheckEqual = /[^!=<>]=[^=<>]/gi; + const rxCheckEqual = /[^!=<>]=[^=<>]/gi; + const compileCheck = function(code) { + try { + //TODO: This should use a cached Function, which should be the same as below. + new Function(`return ${code}`); + } + catch(e) { + return false; + } + return true; + } window.RuleHasError = (rule) => rule.condition.function === "custom" - && rule.condition.data.match(rxCheckEqual); + &&(rule.condition.data.match(rxCheckEqual) + || !compileCheck(rule.condition.data)); window.DefaultRulesError = () => V.defaultRules.some(r => RuleHasError(r)); return DefaultRules; })(); diff --git a/src/js/rulesAssistant.js b/src/js/rulesAssistant.js index a10b2367642d63a911edf29acb5fc964fa9ce593..4f4a1da41b2dd0cf2a7389476262dfa3873cdcb0 100644 --- a/src/js/rulesAssistant.js +++ b/src/js/rulesAssistant.js @@ -209,6 +209,7 @@ window.ruleAppliesP = function ruleAppliesP(cond, slave) { flag = cond.data.value.includes(slave[cond.data.attribute]); break; case "custom": // user provided JS function + //TODO: This should use a cached Function instead of 'eval'ing flag = eval(cond.data)(slave); break; } diff --git a/src/js/rulesAssistantOptions.js b/src/js/rulesAssistantOptions.js index d6cdfc95f72f9f177bc3b735ff8a332b19de2535..7ada7683e3e2835ecb7b4db1cda8b278c30a8d86 100644 --- a/src/js/rulesAssistantOptions.js +++ b/src/js/rulesAssistantOptions.js @@ -10,7 +10,7 @@ window.rulesAssistantOptions = (function() { const noDefaultSetting = {value: "!NDS!", text: "no default setting"}; /** @type {App.RA.Rule} */ - let current_rule; + let current_rule, root; function rulesAssistantOptions(element) { V.nextButton = "Back to Main"; @@ -26,19 +26,19 @@ window.rulesAssistantOptions = (function() { current_rule = V.defaultRules[idx]; } } - const root = new Root(element); + root = new Root(element); } function returnP(e) { return e.keyCode === 13; } - function newRule(root) { + function newRule() { const rule = emptyDefaultRule(); V.defaultRules.push(rule); V.currentRule = rule.ID; - reload(root); + reload(); } - function removeRule(root) { + function removeRule() { const idx = V.defaultRules.findIndex(rule => rule.ID === current_rule.ID); V.defaultRules.splice(idx, 1); if (V.defaultRules.length > 0) { @@ -47,33 +47,33 @@ window.rulesAssistantOptions = (function() { } else { V.currentRule = null; } - reload(root); + reload(); } - function lowerPriority(root) { + function lowerPriority() { if (V.defaultRules.length === 1) { return; } // nothing to swap with const idx = V.defaultRules.findIndex(rule => rule.ID === current_rule.ID); if (idx === 0) { return; } // no lower rule arraySwap(V.defaultRules, idx, idx - 1); - reload(root); + reload(); } - function higherPriority(root) { + function higherPriority() { if (V.defaultRules.length === 1) { return; } // nothing to swap with const idx = V.defaultRules.findIndex(rule => rule.ID === current_rule.ID); if (idx === V.defaultRules.length - 1) { return; } // no higher rule arraySwap(V.defaultRules, idx, idx + 1); - reload(root); + reload(); } - function changeName(name, root) { + function changeName(name) { if (name === current_rule.name) { return; } current_rule.name = name; - reload(root); + reload(); } // reload the passage - function reload(root) { + function reload() { const elem = root.element; elem.innerHTML = ""; rulesAssistantOptions(elem); @@ -905,9 +905,8 @@ window.rulesAssistantOptions = (function() { // rule import field class NewRuleField extends Element { - constructor(root) { + constructor() { super(); - this.root = root; } render() { @@ -940,7 +939,7 @@ window.rulesAssistantOptions = (function() { } else { V.defaultRules.push(App.Entity.Utils.RARuleDatatypeCleanup(rule)); } - reload(this.root); + reload(); } catch (e) { alert(`Couldn't import that rule:\n${e.message}`); } @@ -975,41 +974,40 @@ window.rulesAssistantOptions = (function() { // options displayed when there are no rules class NoRules extends Options { - constructor(root) { + constructor() { super(); - this.root = root; - const newrule = new OptionsItem("Add a new rule", () => { newRule(this.root); }); + const newrule = new OptionsItem("Add a new rule", () => { newRule(); }); this.appendChild(newrule); - const importrule = new OptionsItem("Import a rule", () => { this.root.appendChild(new NewRuleField(this.root)); }); + const importrule = new OptionsItem("Import a rule", () => { root.appendChild(new NewRuleField()); }); this.appendChild(importrule); } } // buttons for selecting the current rule class RuleSelector extends List { - constructor(root) { + constructor() { super("Current rule", V.defaultRules.map(i => [(i.name + (!!RuleHasError(i) ? " <span class='yellow'>[!]</span>" : "")), i]), false); this.setValue(current_rule.name); this.onchange = function(rule) { V.currentRule = rule.ID; - reload(root); + reload(); }; } } // buttons for doing transformations on rules class RuleOptions extends Options { - constructor(root) { + constructor() { super(); - this.appendChild(new OptionsItem("New Rule", () => newRule(root))); - this.appendChild(new OptionsItem("Remove Rule", () => removeRule(root))); + this.appendChild(new OptionsItem("New Rule", newRule)); + this.appendChild(new OptionsItem("Remove Rule", removeRule)); this.appendChild(new OptionsItem("Apply rules", () => this.appendChild(new ApplicationLog()))); - this.appendChild(new OptionsItem("Lower Priority", () => lowerPriority(root))); - this.appendChild(new OptionsItem("Higher Priority", () => higherPriority(root))); - this.appendChild(new OptionsItem("Rename", () => this.appendChild(new RenameField(root)))); + this.appendChild(new OptionsItem("Lower Priority", lowerPriority)); + this.appendChild(new OptionsItem("Higher Priority", higherPriority)); + this.appendChild(new OptionsItem("Rename", () => this.appendChild(new RenameField()))); this.appendChild(new OptionsItem("Export this rule", () => this.appendChild(new ExportField(current_rule)))); this.appendChild(new OptionsItem("Export all rules", () => this.appendChild(new ExportField(...V.defaultRules)))); - this.appendChild(new OptionsItem("Import rule(s)", () => this.appendChild(new NewRuleField(root)))); + this.appendChild(new OptionsItem("Import rule(s)", () => this.appendChild(new NewRuleField()))); } } @@ -1024,10 +1022,10 @@ window.rulesAssistantOptions = (function() { } class RenameField extends Element { - constructor(root) { + constructor() { super(); - this.element.onblur = () => changeName(this.element.value, root); - this.element.onkeypress = (e) => { if (returnP(e)) { changeName(this.element.value, root); } }; + this.element.onblur = () => changeName(this.element.value); + this.element.onkeypress = (e) => { if (returnP(e)) { changeName(this.element.value); } }; } render() { @@ -1196,15 +1194,22 @@ window.rulesAssistantOptions = (function() { render(data) { const elem = document.createElement("div"); const textarea = document.createElement("textarea"); - const errorMessage = document.createElement("div"); - $(errorMessage).addClass("yellow"); textarea.innerHTML = data; - let checkRules = () => errorMessage.innerText = RuleHasError(current_rule) ? "WARNING: This rule attempts to set a variable; please ensure any '=' is replaced with '=='" : ""; - $(textarea).blur(() => current_rule.condition.data = textarea.value); - $(textarea).blur(checkRules); - checkRules(); + $(textarea).blur(() => { + current_rule.condition.data = textarea.value; + //TODO: this would be a good place to cache the Function object that will be used by RuleHasError and ruleAppliesP + reload(); + }); elem.appendChild(textarea); - elem.appendChild(errorMessage); + + if(RuleHasError(current_rule)) + { + const errorMessage = document.createElement("div"); + $(errorMessage).addClass("yellow"); + errorMessage.innerText = "WARNING: There are errors in this condition. Please ensure the syntax is correct and equality is either '==' or '===', not '='"; + elem.appendChild(errorMessage); + } + const explanation = document.createElement("div"); explanation.innerHTML = "Insert a valid <a target='_blank' class='link-external' href='https://www.w3schools.com/js/js_comparisons.asp'>JavaScript comparison and/or logical operation</a>."; elem.appendChild(explanation); diff --git a/src/uncategorized/main.tw b/src/uncategorized/main.tw index 6d4b70e117d0a98493cb4d6f01b5c68efbfc1ee1..481b4988cf4f00abe3718a7da18905884c1efeab 100644 --- a/src/uncategorized/main.tw +++ b/src/uncategorized/main.tw @@ -104,7 +104,7 @@ __''MAIN MENU''__ //[[Summary Options]]// <<else>> | //<<link "Stop applying Rules Assistant at week end" "Main">><<set $rulesAssistantAuto = 0>><</link>>// <</if>> - | //<<if DefaultRulesError()>>@@.yellow; WARNING: some custom rules will change slave variables @@<</if>><<link "Re-apply Rules Assistant now (this will only check slaves in the Penthouse)" "Main">><<for _i = 0;_i < _SL;_i++>><<if $slaves[_i].assignmentVisible == 1 && $slaves[_i].useRulesAssistant == 1>><<= DefaultRules($slaves[_i])>><</if>><</for>><</link>>// + | //<<if DefaultRulesError()>>@@.yellow; WARNING: One or more rules' custom conditions has errors! @@<</if>><<link "Re-apply Rules Assistant now (this will only check slaves in the Penthouse)" "Main">><<for _i = 0;_i < _SL;_i++>><<if $slaves[_i].assignmentVisible == 1 && $slaves[_i].useRulesAssistant == 1>><<= DefaultRules($slaves[_i])>><</if>><</for>><</link>>// <</if>> <<print App.UI.SlaveList.penthousePage()>> diff --git a/src/uncategorized/storyCaption.tw b/src/uncategorized/storyCaption.tw index 518dde903caffca7a85de10eb5aa662715fc903b..2b58dccd294e4df2728f0471ea874f7b7a0438b0 100644 --- a/src/uncategorized/storyCaption.tw +++ b/src/uncategorized/storyCaption.tw @@ -10,7 +10,7 @@ <<link "$nextButton">> <<goto $nextLink>> <</link>> @@.cyan;[Ent]@@ </span> </strong> <<if $rulesAssistantAuto == 1 && DefaultRulesError()>> - <br>@@.yellow;WARNING: some custom rules will change slave variables@@ + <br>@@.yellow;WARNING: Rules Assistant has rules with errors!@@ <</if>> <<else>> <strong> <span id="nextButton"> <<if $nextButton != " ">> <br><br>