// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/ function getNextSibling(elem, selector) { // Get the next sibling element var sibling = elem.nextElementSibling // If there's no selector, return the first sibling if (!selector) return sibling // If the sibling matches our selector, use it // If not, jump to the next sibling and continue the loop while (sibling) { if (sibling.matches(selector)) return sibling sibling = sibling.nextElementSibling } } function createCollapsibles(node) { if (!node) { node = document } let collapsibles = node.querySelectorAll(".collapsible") collapsibles.forEach(function(c) { let handle = document.createElement('span') handle.className = 'collapsible-handle' if (c.className.indexOf('active') !== -1) { handle.innerHTML = '➖' // minus } else { handle.innerHTML = '➕' // plus } c.insertBefore(handle, c.firstChild) c.addEventListener('click', function() { this.classList.toggle("active") let content = getNextSibling(this, '.collapsible-content') if (content.style.display === "block") { content.style.display = "none" handle.innerHTML = '➕' // plus } else { content.style.display = "block" handle.innerHTML = '➖' // minus } if (this == advancedPanelHandle) { let state = (content.style.display === 'block' ? 'true' : 'false') localStorage.setItem(ADVANCED_PANEL_OPEN_KEY, state) } else if (this == modifiersPanelHandle) { let state = (content.style.display === 'block' ? 'true' : 'false') localStorage.setItem(MODIFIERS_PANEL_OPEN_KEY, state) } else if (this == negativePromptPanelHandle) { let state = (content.style.display === 'block' ? 'true' : 'false') localStorage.setItem(NEGATIVE_PROMPT_PANEL_OPEN_KEY, state) } }) }) } function permute(arr) { let permutations = [] let n = arr.length let n_permutations = Math.pow(2, n) for (let i = 0; i < n_permutations; i++) { let perm = [] let mask = Number(i).toString(2).padStart(n, '0') for (let idx = 0; idx < mask.length; idx++) { if (mask[idx] === '1' && arr[idx].trim() !== '') { perm.push(arr[idx]) } } if (perm.length > 0) { permutations.push(perm) } } return permutations } // https://stackoverflow.com/a/8212878 function millisecondsToStr(milliseconds) { function numberEnding (number) { return (number > 1) ? 's' : '' } var temp = Math.floor(milliseconds / 1000) var hours = Math.floor((temp %= 86400) / 3600) var s = '' if (hours) { s += hours + ' hour' + numberEnding(hours) + ' ' } var minutes = Math.floor((temp %= 3600) / 60) if (minutes) { s += minutes + ' minute' + numberEnding(minutes) + ' ' } var seconds = temp % 60 if (!hours && minutes < 4 && seconds) { s += seconds + ' second' + numberEnding(seconds) } return s } // https://rosettacode.org/wiki/Brace_expansion#JavaScript function BraceExpander() { 'use strict' // Index of any closing brace matching the opening // brace at iPosn, // with the indices of any immediately-enclosed commas. function bracePair(tkns, iPosn, iNest, lstCommas) { if (iPosn >= tkns.length || iPosn < 0) return null; var t = tkns[iPosn], n = (t === '{') ? ( iNest + 1 ) : (t === '}' ? ( iNest - 1 ) : iNest), lst = (t === ',' && iNest === 1) ? ( lstCommas.concat(iPosn) ) : lstCommas; return n ? bracePair(tkns, iPosn + 1, n, lst) : { close: iPosn, commas: lst }; } // Parse of a SYNTAGM subtree function andTree(dctSofar, tkns) { if (!tkns.length) return [dctSofar, []]; var dctParse = dctSofar ? dctSofar : { fn: and, args: [] }, head = tkns[0], tail = head ? tkns.slice(1) : [], dctBrace = head === '{' ? bracePair( tkns, 0, 0, [] ) : null, lstOR = dctBrace && ( dctBrace.close ) && dctBrace.commas.length ? ( splitAt(dctBrace.close + 1, tkns) ) : null; return andTree({ fn: and, args: dctParse.args.concat( lstOR ? ( orTree(dctParse, lstOR[0], dctBrace.commas) ) : head ) }, lstOR ? ( lstOR[1] ) : tail); } // Parse of a PARADIGM subtree function orTree(dctSofar, tkns, lstCommas) { if (!tkns.length) return [dctSofar, []]; var iLast = lstCommas.length; return { fn: or, args: splitsAt( lstCommas, tkns ).map(function (x, i) { var ts = x.slice( 1, i === iLast ? ( -1 ) : void 0 ); return ts.length ? ts : ['']; }).map(function (ts) { return ts.length > 1 ? ( andTree(null, ts)[0] ) : ts[0]; }) }; } // List of unescaped braces and commas, and remaining strings function tokens(str) { // Filter function excludes empty splitting artefacts var toS = function (x) { return x.toString(); }; return str.split(/(\\\\)/).filter(toS).reduce(function (a, s) { return a.concat(s.charAt(0) === '\\' ? s : s.split( /(\\*[{,}])/ ).filter(toS)); }, []); } // PARSE TREE OPERATOR (1 of 2) // Each possible head * each possible tail function and(args) { var lng = args.length, head = lng ? args[0] : null, lstHead = "string" === typeof head ? ( [head] ) : head; return lng ? ( 1 < lng ? lstHead.reduce(function (a, h) { return a.concat( and(args.slice(1)).map(function (t) { return h + t; }) ); }, []) : lstHead ) : []; } // PARSE TREE OPERATOR (2 of 2) // Each option flattened function or(args) { return args.reduce(function (a, b) { return a.concat(b); }, []); } // One list split into two (first sublist length n) function splitAt(n, lst) { return n < lst.length + 1 ? [ lst.slice(0, n), lst.slice(n) ] : [lst, []]; } // One list split into several (sublist lengths [n]) function splitsAt(lstN, lst) { return lstN.reduceRight(function (a, x) { return splitAt(x, a[0]).concat(a.slice(1)); }, [lst]); } // Value of the parse tree function evaluated(e) { return typeof e === 'string' ? e : e.fn(e.args.map(evaluated)); } // JSON prettyprint (for parse tree, token list etc) function pp(e) { return JSON.stringify(e, function (k, v) { return typeof v === 'function' ? ( '[function ' + v.name + ']' ) : v; }, 2) } // ----------------------- MAIN ------------------------ // s -> [s] this.expand = function(s) { // BRACE EXPRESSION PARSED var dctParse = andTree(null, tokens(s))[0]; // ABSTRACT SYNTAX TREE LOGGED // console.log(pp(dctParse)); // AST EVALUATED TO LIST OF STRINGS return evaluated(dctParse); } }