diff --git a/js/ue.css b/js/ue.css index ae9610b..47f7697 100644 --- a/js/ue.css +++ b/js/ue.css @@ -52,10 +52,34 @@ content: ':'; } + .ue_editor_body td { + select { + width: 100%; + } + } + .ue_editor_footer { margin: 8px; border: thin solid white; padding: 2px 30px; background-color: rgba(0, 0, 0, 0.75); } + + .regex_input_container { + display: flex; + width: 250px; + justify-content: space-between; + input { + flex-grow: 1; + } + span { + padding: 4px 8px 0px 0px; + font-size: 60%; + } + .checkbox { + flex-grow: 0; + margin-left: 0px; + } + } + } diff --git a/js/ue_properties.js b/js/ue_properties.js index 6b9edc4..55bad33 100644 --- a/js/ue_properties.js +++ b/js/ue_properties.js @@ -29,8 +29,11 @@ export function describe_restrictions(node) { const statements = [] if (node.properties.ue_properties) { ALL_REGEXES.forEach((r)=>{ - const reg = node.properties.ue_properties[`${r}_regex`] - if (reg && reg.length>0) statements.push([`${i18n(r)} regex`, reg]) + var reg = node.properties.ue_properties[`${r}_regex`] + if (reg && reg.length>0) { + const condition = i18n((node.properties.ue_properties[`${r}_regex_invert`]) ? "not match" : "match") + statements.push([`${i18n(r)} regex`, `${condition} ${reg}`]) + } }) if (node.properties.ue_properties.group_restricted) statements.push(['group',i18n(GROUP_RESTRICTION_OPTIONS[node.properties.ue_properties.group_restricted])]) if (node.properties.ue_properties.color_restricted) statements.push(['color',i18n(COLOR_RESTRICTION_OPTIONS[node.properties.ue_properties.color_restricted])]) @@ -62,6 +65,9 @@ const DEFAULT_PROPERTIES = { title_regex : null, input_regex : null, group_regex : null, + title_regex_invert : false, + input_regex_invert : false, + group_regex_invert : false, priority : undefined, repeated_type_rule : 0, string_to_combo : 0, diff --git a/js/ue_properties_editor.js b/js/ue_properties_editor.js index 6782028..26c5e03 100644 --- a/js/ue_properties_editor.js +++ b/js/ue_properties_editor.js @@ -8,6 +8,13 @@ import { shared } from "./shared.js"; const REGEXES = ['title', 'input', 'group'] const P_REGEXES = ['prompt', 'negative'] +function create_element(tag, parent, options={}) { + const elem = document.createElement(tag) + if (parent) parent.appendChild(elem) + Object.assign(elem, options) + return elem +} + export function edit_restrictions(a,b,c,d, node) { edit_window.set_body(create_editor_html(node)) edit_window.set_title(`Restrictions for node #${node.id}`) @@ -61,23 +68,41 @@ function create_editor_html(node) { for (var j=0; j<2; j++) { const name = P_REGEXES[j] const row = add_row(table, `${i18n(name)} regex`) - const input = document.createElement('input') - input.type = "text" - input.value = node.properties.ue_properties[`${name}_regex`] || i18n_functional(`${name}_regex`) + + const input_props = { + type:'text', + id:`${name}_regex_value`, + value: node.properties.ue_properties[`${name}_regex`] || i18n_functional(`${name}_regex`), + } + + const input = create_element('input', contents, input_props). + addEventListener('input', (e)=>{ changed(node, `${name}_regex`, e.target.value)}) + if (!node.properties.ue_properties[`${name}_regex`]) input.style.opacity = 0.5 - input.id = `${name}_regex_value` - input.style.width = '250px' - input.addEventListener('input', ()=>{ changed(node, `${name}_regex`, input.value)}) + add_cell(row,input) } } else { const name = REGEXES[i] const row = add_row(table, `${i18n(name)} regex`) - const input = document.createElement('input') - input.type = "text" - input.value = node.properties.ue_properties[`${name}_regex`] || '' - input.addEventListener('input', ()=>{ changed(node, `${name}_regex`, input.value)}) - add_cell(row,input) + + const contents = create_element('span', null, {'className':'regex_input_container'}) + + const checkbox_props = { + type:'checkbox', + id:`${name}_regex_invert`, + checked: (node.properties.ue_properties[`${name}_regex_invert`]) ? true : undefined, + className: 'checkbox' + } + create_element('input', contents, checkbox_props). + addEventListener('input', (e)=>{ changed(node, `${name}_regex_invert`, e.target.checked); } ) + + create_element('span', contents, {innerText:i18n('Invert'), className:'regex_checkbox_label'}) + + create_element('input', contents, {type:'text', value:node.properties.ue_properties[`${name}_regex`] || ''}). + addEventListener('input', (e)=>{ changed(node, `${name}_regex`, e.target.value)}) + + add_cell(row,contents) } } diff --git a/js/use_everywhere_classes.js b/js/use_everywhere_classes.js index d561fe3..b840f13 100644 --- a/js/use_everywhere_classes.js +++ b/js/use_everywhere_classes.js @@ -15,12 +15,23 @@ export function display_name(node) { function regex_for(node, k) { try { const w0 = node.properties.ue_properties[`${k}_regex`] - return (w0 && w0!='.*') ? new RegExp(w0) : null; + return (w0 && w0!='.*') ? {regex:new RegExp(w0), invert:node.properties.ue_properties[`${k}_regex_invert`]} : null; } catch (e) { return null } } + +const P_REGEXES = ['prompt', 'negative'] +const PROMPT_REGEXES = [new RegExp(i18n_functional('prompt_regex')), new RegExp(i18n_functional('negative_regex'))] + +function prompt_regex(node, i) { + const reg = node.properties.ue_properties[`${P_REGEXES[i]}_regex`] + if (reg) return {regex:new RegExp(reg), invert:node.properties.ue_properties[`${P_REGEXES[i]}_regex_invert`]} + else return {regex:i18n_functional_regex(`${P_REGEXES[i]}_regex`), invert:node.properties.ue_properties[`${P_REGEXES[i]}_regex_invert`]} +} + + /* The UseEverywhere object represents a single 'broadcast'. It generally contains controller - the UE node that controls the broadcase @@ -59,8 +70,8 @@ class UseEverywhere { // for breakpointing throw e; } - if (this.title_regex) this.description += ` - node title regex '${this.title_regex.source}'`; - if (this.input_regex) this.description += ` - input name regex '${this.input_regex.source}'`; + if (this.title_regex) this.description += ` - node title regex '${this.title_regex.regex.source} ${(this.title_regex.invert) ? "(inverted)" : ""}'`; + if (this.input_regex) this.description += ` - input name regex '${this.input_regex.regex.source} ${(this.input_regex.invert) ? "(inverted)" : ""}''`; } sending_differs_from(another_ue) { @@ -87,9 +98,9 @@ class UseEverywhere { const input_label = input.label || input.localized_name || input.name; const node_label = node.title ? node.title : (node.properties['Node name for S&R'] ? node.properties['Node name for S&R'] : node.type); if (this.title_regex) { - if (!(this.title_regex.test(node_label))) return false; + if ( this.title_regex.regex.test(node_label) == this.title_regex.invert ) return false; } - if (node.type=="Highway" && typeof this.input_regex==='string') { // Highway nodes - broken if there are two matches... + /*if (node.type=="Highway" && typeof this.input_regex==='string') { // Highway nodes - broken if there are two matches... const input_label_split = input_label.split(':'); if (input_label_split.length==1) { if (input_label==this.input_regex) { @@ -102,7 +113,7 @@ class UseEverywhere { if ((input_label_split[0]==this.input_regex) && input_label_split[1]==input.type) return true; return false; } - } + }*/ if (this.type != input.type) { if (this.type=="STRING" && input.type=="COMBO" && @@ -112,8 +123,8 @@ class UseEverywhere { return false } } - if (this.input_regex && typeof this.input_regex==='string') return false; // input_regex started '+', which targets Highway nodes only - if (this.input_regex && !this.input_regex.test(input_label)) return false; + //if (this.input_regex && typeof this.input_regex==='string') return false; // input_regex started '+', which targets Highway nodes only + if (this.input_regex && ( this.input_regex.regex.test(input_label)==this.input_regex.invert )) return false; return true; } @@ -254,7 +265,7 @@ export class UseEverywhereList { add_ue_from_node(node) { if (node.properties.ue_properties.seed_inputs) { - this.add_ue(node, -1, "INT", [node.id.toString(),0], regex_for(node, 'input')); + this.add_ue(node, -1, "INT", [node.id.toString(),0], regex_for(node, 'input') ); } else { var the_possibles @@ -319,12 +330,3 @@ export class UseEverywhereList { } } -const P_REGEXES = ['prompt', 'negative'] -const PROMPT_REGEXES = [new RegExp(i18n_functional('prompt_regex')), new RegExp(i18n_functional('negative_regex'))] - -function prompt_regex(node, i) { - const reg = node.properties.ue_properties[`${P_REGEXES[i]}_regex`] - if (reg) return new RegExp(reg) - else return i18n_functional_regex(`${P_REGEXES[i]}_regex`) -} - diff --git a/js/use_everywhere_ui.js b/js/use_everywhere_ui.js index 50523c1..c11b9af 100644 --- a/js/use_everywhere_ui.js +++ b/js/use_everywhere_ui.js @@ -29,7 +29,7 @@ function nodes_not_in_my_group(node) { function nodes_in_groups_matching(regex, already_limited_to) { const nodes_in = new Set(); app.graph._groups.forEach((group) => { - if (regex.test(group.title)) { + if (regex.regex.test(group.title) != regex.invert) { if (!app.canvas.selected_group_moving) group.recomputeInsideNodes(); /* Note for optimisation - it would be more efficient to calculate what nodes are in what groups