Last active
May 21, 2021 13:23
-
-
Save jrr6/a4556759fed1eb9e982a3bc3c0fd1713 to your computer and use it in GitHub Desktop.
A userscript that adds animation and custom parameter inputs to shad.io's matrix visualizer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Interactive Matrix Featurifier | |
| // @author aaplmath | |
| // @version 1.0.0 | |
| // @description Adds ability to run animation and manually set variables on shad.io 3b1b-style matrix visualizer. | |
| // @match https://shad.io/MatVis/ | |
| // @downloadURL https://gist.github.com/jrr6/a4556759fed1eb9e982a3bc3c0fd1713/raw/InteractiveMatrixFeaturifier.js | |
| // @updateURL https://gist.github.com/jrr6/a4556759fed1eb9e982a3bc3c0fd1713/raw/InteractiveMatrixFeaturifier.js | |
| // @grant none | |
| // ==/UserScript== | |
| // ------------- VAR INPUT ------------- | |
| function setStateVar (varName, val) { | |
| let panel = document.getElementsByClassName('panel-body')[0] | |
| let key = Object.keys(panel)[0] // name of React internals object—changes on page load | |
| let instance = panel[key]._renderedChildren['.$.0']._instance | |
| instance.updater.enqueueSetState(instance, {[varName]: val}) | |
| } | |
| function setPropVar (varName, val) { | |
| let panel = document.getElementsByClassName('panel-body')[0] | |
| let key = Object.keys(panel)[0] // name of React internals object—changes on page load | |
| let instance = panel[key]._renderedChildren['.$.0']._instance | |
| instance.props[varName] = val | |
| instance.updater.enqueueForceUpdate(instance) | |
| } | |
| let inputsDiv = document.createElement('div') | |
| inputsDiv.innerHTML = ` | |
| <input type="number" id="a" placeholder="a"> | |
| <input type="number" id="b" placeholder="b"> | |
| <input type="number" id="x" placeholder="x"> | |
| <input type="number" id="c" placeholder="c"> | |
| <input type="number" id="d" placeholder="d"> | |
| <input type="number" id="y" placeholder="y"> | |
| ` | |
| inputsDiv.style.display = 'grid' | |
| inputsDiv.style.gridTemplateColumns = 'repeat(3, 1fr)' | |
| inputsDiv.addEventListener('change', (e) => { | |
| let id = e.target.getAttribute('id') | |
| let val = e.target.value | |
| if (val !== '' && val !== undefined && val !== null) { | |
| setStateVar(e.target.getAttribute('id'), parseFloat(e.target.value)) | |
| e.target.value = '' | |
| } | |
| }, true) | |
| document.getElementById('mainContainer').insertBefore(inputsDiv, document.getElementsByClassName('panel')[0]) | |
| // ------------- ANIMATION ------------- | |
| /** | |
| * Runs the simulation automatically. | |
| * @param {boolean} inc whether to increment time (play forward; true) or decrement time (play backward; false). | |
| * @param {number} iterDelta how much to progress time by (where total time = 0–1) on each iteration. | |
| * @param {number} updateFreqMS how frequently to update. | |
| */ | |
| function runSim (inc) { | |
| let iterDelta = parseFloat(document.getElementById('delta').value) | |
| let updateFreqMS = parseFloat(document.getElementById('freq').value) | |
| let slide = document.querySelector('input.form-control') | |
| let val = parseFloat(slide.value) | |
| if ((inc && val < 1) || (!inc && val > 0)) { | |
| let newVal = parseFloat(slide.value) + (inc ? iterDelta : -iterDelta) | |
| // setting it to 1 or 0 means we never actually get to the end…idek this website is weird | |
| if (newVal >= 1) newVal = 0.99999999 | |
| if (newVal <= 0) newVal = 1e-10 | |
| slide.value = newVal | |
| Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set.call(slide, slide.value) | |
| slide.dispatchEvent(new Event('input', { bubbles: true })) | |
| let obj = window.setTimeout(() => { inc ? runSim(true) : runSim(false) }, updateFreqMS) | |
| ref = { | |
| cancel: () => { clearTimeout(obj) } | |
| } | |
| } | |
| } | |
| function safeCancel () { | |
| if (ref && ref.cancel) ref.cancel() | |
| } | |
| let ref | |
| let slide = document.querySelector('input.form-control') | |
| slide.style.display = 'inline' | |
| slide.style.width = '70%' | |
| // move the t label out of the way | |
| let container = document.querySelector('.col-sm-11') | |
| let label = document.querySelector('label.control-label') | |
| container.insertBefore(label, document.querySelector('.col-sm-11 div:last-child')) | |
| label.style.maxWidth = 'unset' | |
| label.style.position = 'absolute' | |
| let div = slide.parentNode | |
| let buttonDiv = document.createElement('div') | |
| buttonDiv.style.display = 'inline' | |
| buttonDiv.innerHTML = ` | |
| <button id="play-reverse">◄</button> | |
| <button id="pause">▌▌</button> | |
| <button id="play-forward">►</button> | |
| ` | |
| div.insertBefore(buttonDiv, div.firstChild) | |
| let inputDiv = document.createElement('div') | |
| inputDiv.innerHTML = ` | |
| Time 𝚫 per iteration: <input type="number" value="0.0125" id="delta"> | |
| Iteration freq (ms): <input type="number" value="25" id="freq"> | |
| Scale (temp): <input type="number" placeholder="60" id="scale"> | |
| ` | |
| div.appendChild(inputDiv) | |
| document.getElementById('play-reverse').addEventListener('click', (e) => { e.preventDefault(); safeCancel(); runSim(false) }, false) | |
| document.getElementById('play-forward').addEventListener('click', (e) => { e.preventDefault(); safeCancel(); runSim(true) }, false) | |
| document.getElementById('pause').addEventListener('click', (e) => { e.preventDefault(); safeCancel() }, false) | |
| // TODO: scale doesn't take upon animating | |
| let scale = document.getElementById('scale') | |
| scale.addEventListener('change', (e) => { e.preventDefault(); setPropVar('scale', parseFloat(scale.value)); scale.value = '' }, false) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment