diff options
Diffstat (limited to 'searcher')
-rw-r--r-- | searcher/index.html | 150 | ||||
-rw-r--r-- | searcher/script.js | 270 | ||||
-rw-r--r-- | searcher/style.css | 237 |
3 files changed, 657 insertions, 0 deletions
diff --git a/searcher/index.html b/searcher/index.html new file mode 100644 index 0000000..f6ae0a1 --- /dev/null +++ b/searcher/index.html @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<html lang="en-us"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width"> + <meta name="description" content="A simple utility to help you work out what chips do."> + <meta name="author" content="@✨Aleteoryx✨#1027"> + <title>CV2 Chip Searcher</title> + <link crossorigin href="style.css" rel="stylesheet" type="text/css" /> + <link crossorigin href="/lib/chips.css" rel="stylesheet" type="text/css" /> + + <link crossorigin href="/lib/util.js" rel="preload" as="script"/> + <link crossorigin href="/lib/chips.js" rel="preload" as="script"/> + <link crossorigin href="script.js" rel="preload" as="script"/> + <link crossorigin href="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/6.4.6/fuse.min.js" rel="preload" as="script"/> + <link crossorigin href="/lib/chips.css" rel="preload" as="style"/> + <link crossorigin href="style.css" rel="preload" as="style"/> + <link crossorigin href="/terms.json" rel="preload" as="fetch"/> + <link crossorigin href="/circuitsv2.json" rel="preload" as="fetch"/> + <link crossorigin href="https://raw.githubusercontent.com/tyleo-rec/CircuitsV2Resources/master/misc/circuitsv2.json" rel="preload" as="fetch"/> + + <script crossorigin src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/6.4.6/fuse.min.js"></script> + <script crossorigin src="/lib/util.js"></script> + <script crossorigin src="/lib/chips.js"></script> + <script crossorigin src="script.js"></script> + <noscript><style> #form, hr, #listcontrols, #helpbox, #resultslist { display: none } </style></noscript> + <datalist id="paletteSearch"> + </datalist> + </head> + <body> + <noscript> + The chip searcher requires JavaScript to function. + If you cannot enable it, you can try + <kbd>Crtl</kbd>+<kbd>F</kbd>ing your way through + <a href="https://raw.githubusercontent.com/tyleo-rec/CircuitsV2Resources/master/misc/circuitsv2.json"> + the actual chip JSON file + </a>. + + </noscript> + <div id="imgbox" style="display:none;"></div> + <form id="form" autocomplete="off"> + <input type="text" id="search" placeholder="Search for a chip." list="paletteSearch"/> + <hr/> + <details> + <summary>Options</summary> + <div> + <input type="checkbox" name="refresh" id="autorefresh" checked/> + <label for="autorefresh">Auto Refresh</label> + <input type="checkbox" name="beta" id="beta" checked/> + <label for="beta">Show Beta Chips</label> + <input type="checkbox" name="depr" id="deprecated"/> + <label for="deprecated">Show Deprecated Chips</label> + <input type="checkbox" name="filterSug" id="fsug" checked> + <label for="fsug">Enable Filter Suggestions</label> + </div> + <hr/> + <div> + <p>Fuzzy Finder:</p> + <input type="radio" name="fuzziness" value='{"thresh":0,"nummod":1}' id="fuzzy0"/> + <label for="fuzzy0">Exact Match</label> + <input type="radio" name="fuzziness" value='{"thresh":0.3,"nummod":0.95}' id="fuzzy1" checked/> + <label for="fuzzy1">Small Typos</label> + <input type="radio" name="fuzziness" value='{"thresh":0.6,"nummod":0.9}' id="fuzzy2"/> + <label for="fuzzy2">Loose Match</label> + </div> + <hr/> + <div> + <p>Search By:</p> + <input type="checkbox" name="ReadonlyPaletteName" id="key0" checked/> + <label for="key0">Chip Name</label> + <input type="checkbox" name="Description" id="key1"/> + <label for="key1">Chip Description</label> + </div> + <hr/> + <div> + <label for="cfgitems">Items Per Page: </label> + <input type="number" name="items" id="cfgitems" value="12" step="1"> + </div> + </details> + </form> + <hr/> + <div id="listcontrols"> + <button id="first" onclick="first()"><<</button> + <button id="prev" onclick="prev()"><</button> + <span>Showing page <span id="pagen">0</span>/<span id="of">0</span>.</span> + <button id="next" onclick="next()">></button> + <button id="last" onclick="last()">>></button> + </div> + <hr/> + <div id="resultslist"></div> + <p id="grapherlink">Also check out <a href="/grapher">The graphing tool</a>!</p> + <p>Maintained by <a href="https://rec.net/user/winrg" target="_blank" rel="noopener noreferrer">@winrg</a>/<code>@✨Aleteoryx, Keeper of Names✨#1027</code></p> + <details id="helpbox"> + <summary>?</summary> + <div> + <!-- Unused tabbing system. Ignore. + <input type="radio" name="test" class="selecttab" checked/> + <p class="tab">text for button 1</p> + <input type="radio" name="test" class="selecttab"/> + <p class="tab">text for button 2</p> + <input type="radio" name="test" class="selecttab"/> + <p class="tab">text for button 3</p> + <input type="radio" name="test" class="selecttab"/> + <p class="tab">text for button 4</p>--> + <h2>The Basics</h2> + <hr/> + <p>The chip searcher is a simple program. + To get started, simply type the name of the chip you'd like to know about into the top box. + By default, the searcher will automatically refresh the results as you type.</p> + <h4>Different types of chips</h4> + <hr> + <ul> + <li><b>Beta Chips</b> are indicated by a β symbol.</li> + <li><b>Soon-to-be deprecated Chips</b> are indicated by yellow text, as well as a warning.</li> + <li><b>Deprecated Chips</b> are indicated by red text, as well as a warning.</li> + </ul> + <p>Neither form of deprecated chip will display by default.</p> + <h2>Options</h2> + <hr/> + <p>The chip searcher supports the following configuration + options, contained in the fold-out menu below the search bar.</p> + <h4>Toggles</h4> + <hr/> + <ul> + <li><b>Auto Refresh</b>: Automatically update search results as you type. + When disabled, hit <kbd>Enter</kbd> to refresh.</li> + <li><b>Show Beta Chips</b>: When enabled, beta chips will be included in the search.</li> + <li><b>Show Deprecated Chips</b>: When enabled, deprecated chips will be included in the search.</li> + <li><b>Enable Filter Suggestions</b>: When enabled, filters will be displayed as autocomplete options.</li> + </ul> + <h4>Fuzzy Finder</h4> + <hr/> + <ul> + <li><b>Exact Match</b>: What is typed must appear identically in the results.</li> + <li><b>Small Typos</b>: Small errors are ignored, though larger ones will still affect the results.</li> + <li><b>Loose Match</b>: Really only useful if you can't spell.</li> + </ul> + <h4>Search By</h4> + <hr/> + <ul> + <li><b>Name</b>: Search for what is typed in the chip name.</li> + <li><b>Description</b>: Search for what is typed in the chip description</li> + </ul> + <h4>Items Per Page</h4> + <hr/> + <p>Configures the number of items that will appear in each page. Defaults to 12.</p> + </div> + </details> + </body> +</html>
\ No newline at end of file diff --git a/searcher/script.js b/searcher/script.js new file mode 100644 index 0000000..d80cd8f --- /dev/null +++ b/searcher/script.js @@ -0,0 +1,270 @@ +const isNested = Boolean(window.parent != window); + +const deprecationStrings = { + "Active": {Text: "", Class: "hide"}, + "": {Text: "Warning! This chip is being deprecated. It will be broken in the near future, and should not be used.", Class: "depwarn"}, + "Deprecated": {Text: "Warning! This chip is currently deprecated. It is no longer in the palette, and likely does not work.", Class: "depbad"} +}; +const fusenameopts = { + isCaseSensitive: false, + minMatchCharLength: 1, + ignoreLocation: true, + threshold: 0, + keys: ["ReadonlyPaletteName", "ReadonlyChipName"], + includeScore: true +}; +const fusedescopts = { + isCaseSensitive: false, + minMatchCharLength: 1, + ignoreLocation: true, + threshold: 0, + keys: ["Description"], + includeScore: true +}; + +function genExplanation(name) { + return name; +} + +function genSearchPath(path, name) { + const ret = document.createElement("li"); + ret.classList.add("searchpath"); + ret.innerText = path.join("/") + "/" + name; + return ret; +} + +function getChipAddListener(GUID) { + return function(e) { + window.parent.postMessage( + { + type: 'newChip', + GUID: GUID + } + ); + } +} + +var searchresults = []; +var page = 0 + +window.addEventListener("load", async (e) => { + const search = document.getElementById("search"); + const form = document.getElementById("form"); + const suggestions = document.getElementById("paletteSearch"); + + if (isNested) document.getElementById("grapherlink").remove(); + + if (localStorage.length) { + form.depr.checked = localStorage.getItem("depr"); + form.beta.checked = localStorage.getItem("beta"); + form.ReadonlyPaletteName.checked = localStorage.getItem("ReadonlyPaletteName"); + form.Description.checked = localStorage.getItem("Description"); + form.refresh.checked = localStorage.getItem("refresh"); + form.fuzziness.value = localStorage.getItem("fuzz"); + form.items.value = localStorage.getItem("items"); + form.filterSug.checked = localStorage.getItem("fsug"); + } + + let v2pr = fetch(/*"https://raw.githubusercontent.com/tyleo-rec/CircuitsV2Resources/master/misc/circuitsv2.json"/*/"/circuitsv2.json") + .then(res => res.json()); + let termspr = fetch("/terms.json") + .then(res => res.json()); + + [v2json, termsjson] = await Promise.all([v2pr, termspr]); + //console.log(v2json, termsjson); + + + //GUID needs to be in the same object now, because we convert to an array + const searchable = Object.entries(v2json.Nodes) + .map(pair => {return {GUID: pair[0], ...pair[1]}}) + searchable.sort((a, b) => (a.ReadonlyPaletteName.toLowerCase() > b.ReadonlyPaletteName.toLowerCase()) ? 1 : -1); + + const fuses = [new Fuse(searchable, fusenameopts), + new Fuse(searchable, fusedescopts)]; + + const filterTree = {nodes: searchable}; + searchable.forEach(chip => { + chip.NodeFilters.forEach(filter => { + var currentNode = filterTree; + filter.FilterPath.forEach(path => { + if (!currentNode[path.toUpperCase()]) currentNode[path.toUpperCase()] = {nodes: []}; + currentNode = currentNode[path.toUpperCase()] + if (!currentNode.nodes.includes(chip))currentNode.nodes.push(chip); + currentNode.actualname = path; + }) + }) + }) + + //console.log(filterTree); + + const redraw = targ => { + const fuseinuse = fuses.filter(fuse => form[fuse.options.keys[0]].checked); + + start = performance.now(); + + localStorage.setItem("depr", form.depr.checked ? "set" : ""); + localStorage.setItem("beta", form.beta.checked ? "set" : ""); + localStorage.setItem("ReadonlyPaletteName", form.ReadonlyPaletteName.checked ? "set" : ""); + localStorage.setItem("Description", form.Description.checked ? "set" : ""); + localStorage.setItem("refresh", form.refresh.checked ? "set" : ""); + localStorage.setItem("fsug", form.filterSug.checked ? "set" : ""); + localStorage.setItem("fuzz", form.fuzziness.value); + localStorage.setItem("items", form.items.value); + + suggestions.id = form.filterSug.checked ? "paletteSearch" : "" + + var content2 = filterTree; + + const fullPath = targ.value.replace(/\\+|\/+/, "/") + .split("/"); + const startOfPath = fullPath.slice(0, -1); + const endOfPath = fullPath.at(-1); + const startOfSuggestion = startOfPath.join('/'); + + startOfPath.forEach(path => { + try { + content2 = content2[path.toUpperCase()] + } catch (e) {} + }); + + if (!content2) content2 = {nodes: []}; + + suggestions.innerText = ''; + suggestions.append(...Object.keys(content2) + .filter(i => i.toUpperCase() == i) + .sort((a, b) => (a > b) ? 1 : -1) + .map(i => { + var e = document.createElement('option'); + e.value = startOfSuggestion + (startOfSuggestion ? '/' : '') + content2[i].actualname + '/'; + return e; + }) + ) + + + fuseinuse.forEach(fuse => { + fuse.options.minMatchCharLength = Math.floor(endOfPath.length * JSON.parse(form.fuzziness.value).nummod); + fuse.options.threshold = JSON.parse(form.fuzziness.value).thresh; + fuse.setCollection(content2.nodes); + }); + + var content = endOfPath ? (() => { + let m = fuseinuse.map(fuse => fuse.search(endOfPath)).flat() + m.sort((a,b) => a.score - b.score); + m = Array.from(new Set(m.map(e => e.item))); + return m; + })() : content2.nodes; + //console.log(content2.nodes) + + content = content.map(el => { + if ((form.depr.checked || el.DeprecationStage == 0) && (form.beta.checked || !el.IsBetaChip)) { + const ret = newEl("details", "returnedchip"); + if (el.IsBetaChip) ret.classList.add("betaChip"); + + const iret = newEl("div", "infocontainer"); + + const name = document.createElement("summary"); + name.innerText = el.ReadonlyPaletteName + + ((el.ReadonlyPaletteName == el.ReadonlyChipName) ? "" : ` - aka "${el.ReadonlyChipName}"`); + + const deprinfo = deprecationStrings[el.DeprecationStage]; + const depr = newEl("div", deprinfo.Class); + depr.innerText = deprinfo.Text; + + const desc = newEl("p", "chipdesc") + desc.innerText = el.Description == "" ? "No Description!" : el.Description; + + const filters = newEl("ul", "filters") + filters.append(...Object.values(el.NodeFilters).map(val => genSearchPath(val.FilterPath, el.ReadonlyPaletteName))); + + iret.append(desc, filters); + + const addBtn = newEl('button', 'addBtn'); + addBtn.innerText = "+"; + addBtn.setAttribute('title', 'Add to graph'); + addBtn.onclick = getChipAddListener(el.GUID); + if (isNested) name.append(addBtn); + + let m = generateChipHTML(el.NodeDescs); + + ret.append(depr, name, iret, m); + + return ret; + } + }).filter(e => e != undefined); + var perfstring = `returned in ${parseInt(performance.now() - start)} ms.` + display(perfstring, content); + }; + + const redrawHandler = e => redraw(search); + + form.addEventListener("submit", e => {console.log(e); e.preventDefault()}); + search.addEventListener("change", redrawHandler); + form.addEventListener("input", redrawHandler); + + form.refresh.addEventListener("change", e => { + if (e.target.checked) { + form.addEventListener("input", redrawHandler); + } else { + form.removeEventListener("input", redrawHandler); + } + }); + + try { + redraw(search); + } catch (error) { + setTimeout(() => redraw(search), 1000); + } +}); + +function display(perf, content) { + searchresults = content; + page = 0; + update(); +} + +function first() { + page = 0; + update(); +} +function prev() { + page = Math.max(page - 1, 0); + update(); +} + +function next() { + const pagesize = parseInt(document.getElementById("cfgitems").value); + + page = (((page + 1) * pagesize) > searchresults.length) ? page : (page + 1); + update(); +} + +function last() { + const pagesize = parseInt(document.getElementById("cfgitems").value); + + page = Math.max(parseInt((searchresults.length - 1) / pagesize), 0); + update(); +} + + +function update() { + const ndisplay = document.getElementById("pagen"); + const ofdisplay = document.getElementById("of"); + const output = document.getElementById("resultslist"); + const pagesel = document.getElementById("cfgitems"); + + //pagesel.value = ; + + const pagesize = pagesel.value == "" ? 1 : Math.max(parseInt(pagesel.value), 1); + + const start = page * pagesize; + const end = Math.min(searchresults.length, start + pagesize); + + ndisplay.innerText = page + 1; + ofdisplay.innerText = parseInt((searchresults.length - 1) / pagesize) + 1; + + //console.log(start, end); + + output.innerHTML = ""; + output.append(...searchresults.slice(start, end)); +} + diff --git a/searcher/style.css b/searcher/style.css new file mode 100644 index 0000000..d45a04a --- /dev/null +++ b/searcher/style.css @@ -0,0 +1,237 @@ +@import url('https://fonts.googleapis.com/css2?family=Raleway&display=swap'); + +body { +} +html { + --foreforeground: #3788ae; + --foreground: #082f41; + --background: #03141c; + color: white; + background: var(--background); + /*padding: 1cm;*/ + font-size: 12pt; + font-family: 'Raleway', sans-serif; + height: 100%; +} + +hr { + border: 0.5mm solid var(--foreforeground); + background: var(--foreforeground); + width: 100%; + height: 0%; + +} + +.filters::before { + content: "Pallette Search paths:"; +} + +.hide {display: none;} + +input, button, details{ + color: white; + background: var(--foreground); + padding: 1rem; + + border: none; + border-radius: 0.5cm; + font-size: 13pt; + margin: 0.5rem; +} + +input::placeholder { + color: var(--foreforeground); +} + +details :is(input, button) { + background: var(--background); +} + +/*input[type=checkbox] { + display: none; +} + +input[type=checkbox] + label { + position: relative; + padding-left: 2.5rem; + padding-right: .5rem; +} + +input[type=checkbox] + label::before { + background: var(--foreground); + width: 1.5rem; + height: 1.5rem; + margin: auto; + content: ""; + position: absolute; + left: 0; + top: -0.33rem; + border-radius: 0.25rem; + border: 0.25rem var(--foreforeground) solid; +} + + +input[type=checkbox]:checked + label::before { + border-color: var(--foreground); + background: var(--foreforeground); + color: var(--foreground); + font-size: 16pt; + /*content: "✓";* / + align-content: center; +}*/ + +details { + padding: 1rem; + background: var(--foreground); +} + +details.betaChip > summary::before { + content: "β "; + color: paleturquoise; +} + +.returnedchip > summary { + position: relative; +} + +.returnedchip > summary > button.addBtn { + padding: 0rem; + margin: 0; + height: 2rem; + width: 2rem; + position: absolute; + top: calc(50% - 1rem); + right: 0; + left: calc(100% - 2rem); + bottom: calc(50% - 1rem); + text-align: center; + background: #0000; + font-size: 2rem; +} + +details[open] > summary { + border-bottom: 1mm var(--foreforeground) solid; + margin-bottom: 0.5rem; +} + +body { + display: flex; + flex-flow: column; + height: calc(100% - 2cm); + margin: 1cm; +} + +#resultslist { + overflow: auto; +} + +#listcontrols { + display: flex; + flex-flow: row; + align-items: center; +} + + +#helpbox > summary { + height: 1.5cm; + width: 1.5cm; + display: flex; + background: #0000; + border: #fff7 solid 0.9mm; + color: #fff7; + border-radius: 1.5cm; + padding: 0; + font-size: 1.5cm; + align-items: center; + justify-content: center; + float: left; + position: absolute; + bottom: 0; + right: 0; + user-select: none; +} +#helpbox[open] > summary { + margin-bottom: 0; + background: #fff7; + border: #0000 solid 0.9mm; + color: #fff; +} + +#helpbox { + position: fixed; + bottom: 2rem; + right: 2rem; + background: #0000; + border-radius: 0; +} + +#helpbox > div { + background: var(--foreground); + border-radius: 0.5cm; + height: 60vh; + width: 10cm; + overflow-x: auto; + padding: 1rem; + position: relative; + bottom: 2cm; + box-shadow: black 1mm 1mm 3mm +} +a:link { + color: white; +} +a:visited { + color: var(--foreforeground); +} + +.depbad, .depbad + summary { + color: palevioletred; +} +.depwarn, .depwarn + summary { + color: palegoldenrod; +} + +kbd { + background: white; + color: black; + padding: 0.5mm; + border-radius: 1mm; + box-shadow: inset black -0.25mm -0.25mm 1mm; +} + +input:not(:checked).selecttab + .tab { + display: none; +} + +.tab { + position: absolute; + top: 2rem; +} + +summary, +button, +:is(input[type=radio], input[type=checkbox]), +:is(input[type=radio], input[type=checkbox]) + label { + cursor: pointer; +} + + +@media (orientation: portrait) { + #listcontrols { + justify-content: space-between; + } + #form > details { + overflow: auto; + max-height: 20vh; + } + label::after { + content: "\a"; + white-space: pre; + } + input[type=text] { + width: calc(100vw - 3.25cm); + } + #helpbox > div { + width: calc(100vw - 3.5cm); + height: 60vh; + } +}
\ No newline at end of file |