2022-10-18 18:58:04 +02:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-19 05:49:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* Panel Stuff */
|
|
|
|
|
|
|
|
// true = open
|
|
|
|
var COLLAPSIBLES_INITIALIZED = false;
|
|
|
|
const COLLAPSIBLES_KEY = "collapsibles";
|
|
|
|
const COLLAPSIBLE_PANELS = []; // filled in by createCollapsibles with all the elements matching .collapsible
|
|
|
|
|
|
|
|
// on-init call this for any panels that are marked open
|
|
|
|
function toggleCollapsible(element) {
|
|
|
|
var collapsibleHeader = element.querySelector(".collapsible");
|
2022-10-19 07:13:45 +02:00
|
|
|
var handle = element.querySelector(".collapsible-handle");
|
2022-10-19 05:49:58 +02:00
|
|
|
collapsibleHeader.classList.toggle("active")
|
|
|
|
let content = getNextSibling(collapsibleHeader, '.collapsible-content')
|
|
|
|
if (content.style.display === "block") {
|
|
|
|
content.style.display = "none"
|
|
|
|
handle.innerHTML = '➕' // plus
|
|
|
|
} else {
|
|
|
|
content.style.display = "block"
|
|
|
|
handle.innerHTML = '➖' // minus
|
|
|
|
}
|
|
|
|
|
2022-10-19 07:13:45 +02:00
|
|
|
if (COLLAPSIBLES_INITIALIZED && COLLAPSIBLE_PANELS.includes(element)) {
|
2022-10-20 06:12:01 +02:00
|
|
|
saveCollapsibles()
|
2022-10-19 05:49:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function saveCollapsibles() {
|
2022-10-20 06:12:01 +02:00
|
|
|
var values = {}
|
2022-10-19 05:49:58 +02:00
|
|
|
COLLAPSIBLE_PANELS.forEach(element => {
|
2022-10-20 06:12:01 +02:00
|
|
|
var value = element.querySelector(".collapsible").className.indexOf("active") !== -1
|
|
|
|
values[element.id] = value
|
|
|
|
})
|
|
|
|
localStorage.setItem(COLLAPSIBLES_KEY, JSON.stringify(values))
|
2022-10-19 05:49:58 +02:00
|
|
|
}
|
|
|
|
|
2022-10-18 18:58:04 +02:00
|
|
|
function createCollapsibles(node) {
|
2022-10-20 06:12:01 +02:00
|
|
|
var save = false
|
2022-10-18 18:58:04 +02:00
|
|
|
if (!node) {
|
2022-10-20 06:12:01 +02:00
|
|
|
node = document
|
|
|
|
save = true
|
2022-10-18 18:58:04 +02:00
|
|
|
}
|
|
|
|
let collapsibles = node.querySelectorAll(".collapsible")
|
|
|
|
collapsibles.forEach(function(c) {
|
2022-10-19 05:49:58 +02:00
|
|
|
if (save && c.parentElement.id) {
|
2022-10-20 06:12:01 +02:00
|
|
|
COLLAPSIBLE_PANELS.push(c.parentElement)
|
2022-10-19 05:49:58 +02:00
|
|
|
}
|
2022-10-18 18:58:04 +02:00
|
|
|
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() {
|
2022-10-20 06:12:01 +02:00
|
|
|
toggleCollapsible(c.parentElement)
|
|
|
|
})
|
|
|
|
})
|
2022-10-19 05:49:58 +02:00
|
|
|
if (save) {
|
2022-10-20 06:12:01 +02:00
|
|
|
var saved = localStorage.getItem(COLLAPSIBLES_KEY)
|
2022-10-22 02:13:13 +02:00
|
|
|
if (!saved) {
|
|
|
|
saved = tryLoadOldCollapsibles();
|
|
|
|
}
|
2022-10-19 05:49:58 +02:00
|
|
|
if (!saved) {
|
2022-10-20 06:12:01 +02:00
|
|
|
saveCollapsibles()
|
|
|
|
saved = localStorage.getItem(COLLAPSIBLES_KEY)
|
2022-10-19 05:49:58 +02:00
|
|
|
}
|
2022-10-20 06:12:01 +02:00
|
|
|
var values = JSON.parse(saved)
|
2022-10-19 07:13:45 +02:00
|
|
|
COLLAPSIBLE_PANELS.forEach(element => {
|
2022-10-20 06:12:01 +02:00
|
|
|
var value = element.querySelector(".collapsible").className.indexOf("active") !== -1
|
2022-10-19 07:13:45 +02:00
|
|
|
if (values[element.id] != value) {
|
2022-10-20 06:12:01 +02:00
|
|
|
toggleCollapsible(element)
|
2022-10-18 18:58:04 +02:00
|
|
|
}
|
2022-10-19 07:13:45 +02:00
|
|
|
})
|
2022-10-20 06:12:01 +02:00
|
|
|
COLLAPSIBLES_INITIALIZED = true
|
2022-10-19 05:49:58 +02:00
|
|
|
}
|
2022-10-18 18:58:04 +02:00
|
|
|
}
|
|
|
|
|
2022-10-22 02:13:13 +02:00
|
|
|
function tryLoadOldCollapsibles() {
|
|
|
|
var old_map = {
|
|
|
|
"advancedPanelOpen": "editor-settings",
|
|
|
|
"modifiersPanelOpen": "editor-modifiers",
|
|
|
|
"negativePromptPanelOpen": "editor-inputs-prompt"
|
|
|
|
};
|
|
|
|
if (localStorage.getItem(Object.keys(old_map)[0])) {
|
|
|
|
var result = {};
|
|
|
|
Object.keys(old_map).forEach(key => {
|
|
|
|
var value = localStorage.getItem(key);
|
|
|
|
if (value !== null) {
|
|
|
|
result[old_map[key]] = value == true || value == "true"
|
|
|
|
localStorage.removeItem(key)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
result = JSON.stringify(result)
|
|
|
|
localStorage.setItem(COLLAPSIBLES_KEY, result)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-10-18 18:58:04 +02:00
|
|
|
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
|
2022-10-19 10:20:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2022-10-20 13:52:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function asyncDelay(timeout) {
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
setTimeout(resolve, timeout, true)
|
|
|
|
})
|
|
|
|
}
|
2022-11-04 15:18:34 +01:00
|
|
|
|
|
|
|
function preventNonNumericalInput(e) {
|
|
|
|
e = e || window.event;
|
|
|
|
let charCode = (typeof e.which == "undefined") ? e.keyCode : e.which;
|
|
|
|
let charStr = String.fromCharCode(charCode);
|
|
|
|
let re = e.target.getAttribute('pattern') || '^[0-9]+$'
|
|
|
|
re = new RegExp(re)
|
|
|
|
|
|
|
|
if (!charStr.match(re)) {
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
}
|