SlimRunner

v1-0-1-DesmosColorPicker

May 23rd, 2020
161
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * Author: SlimRunner
  3.  *
  4.  * Console script to help change colors of individual graphs easily
  5.  *
  6. */
  7.  
  8. (function () {
  9.     /***************************************************************************/
  10.     // DATA AND OBJECTS
  11.  
  12.     //Object tree of stylesheet
  13.     const guiCSS = {
  14.         controls : [{
  15.             name : 'style',
  16.             id : 'customSheet',
  17.             attributes : [
  18.                 {name: 'type', value: 'text/css'}
  19.             ],
  20.             textContent : '.sli-color-button {background:#ededed;position:fixed;left:0;top:0;width:38px;height:38px;z-index:99;visibility:hidden;opacity:0;transition:opacity 0.1s ease-out}'
  21.         }]
  22.     }
  23.  
  24.     // Object tree of GUI elements
  25.     const guiElements = {
  26.         controls : [{
  27.             name : 'input',
  28.             id : 'colorButton',
  29.             attributes: [
  30.                 {name: 'type', value: 'color'}
  31.             ],
  32.             classes : [
  33.                 'sli-color-button'
  34.             ]
  35.         }]
  36.     }
  37.  
  38.     /***************************************************************************/
  39.     //MAIN CODE
  40.  
  41.     const GUI_GAP = 8;
  42.  
  43.     let styleNode = [];
  44.     // adds a stylesheet to the head element
  45.     insertNodes(guiCSS, document.head, styleNode);
  46.  
  47.     // initializes an array to hold the DOM objects (controls)
  48.     let ctrlNodes = [];
  49.     // furnishes the control list and also adds the elements to the DOM
  50.     insertNodes(guiElements, document.body, ctrlNodes);
  51.  
  52.     let currMenuItem = null;
  53.     let currMenuElement = null;
  54.  
  55.     hookMenu( (itemElem, expItem, isFound) => {
  56.         if (isFound) {
  57.             currMenuItem = expItem;
  58.             currMenuElement = itemElem;
  59.             setButtonLocation();
  60.         }
  61.        
  62.         showButton(isFound);
  63.        
  64.     });
  65.    
  66.     // colorButton click event
  67.     ctrlNodes.colorButton.addEventListener('mousedown', () => {
  68.         ctrlNodes.colorButton.focus();
  69.         ctrlNodes.colorButton.value = getCurrentColor();
  70.         ctrlNodes.colorButton.click();
  71.     });
  72.  
  73.     ctrlNodes.colorButton.addEventListener('change', () => {
  74.         if (currMenuItem.type === 'expression') {
  75.             Calc.setExpression({
  76.                 id: currMenuItem.id,
  77.                 color: ctrlNodes.colorButton.value
  78.             });
  79.         } else if (currMenuItem.type === 'table') {
  80.             let expr = Calc.getExpressions();
  81.            
  82.             expr[getCurrentIndex()].columns[currMenuItem.colIndex].color = ctrlNodes.colorButton.value;
  83.            
  84.             Calc.setExpression({
  85.                 type:'table',
  86.                 id: currMenuItem.id,
  87.                 columns: expr[getCurrentIndex()].columns
  88.             });
  89.         }
  90.        
  91.     });
  92.  
  93.     /***************************************************************************/
  94.     //FUNCTIONS
  95.  
  96.     // shows or hides button to access custom properties
  97.     function showButton(value) {
  98.         if (value) {
  99.             ctrlNodes.colorButton.style.visibility = 'visible';
  100.             ctrlNodes.colorButton.style.opacity = '1';
  101.         } else {
  102.             ctrlNodes.colorButton.style.visibility = 'hidden';
  103.             ctrlNodes.colorButton.style.opacity = '0';
  104.         }
  105.     }
  106.  
  107.     function setButtonLocation() {
  108.         let mnu = currMenuElement.getBoundingClientRect();
  109.         let btn = ctrlNodes.colorButton.getBoundingClientRect();
  110.        
  111.         let x = (mnu.right + GUI_GAP);
  112.         let y = (mnu.bottom - (mnu.height + btn.height) / 2);
  113.        
  114.         ctrlNodes.colorButton.style.left = `${x}px`;
  115.         ctrlNodes.colorButton.style.top = `${y}px`;
  116.     }
  117.  
  118.     //returns true if the index references a valid expression type item and the color is a valid HTML hex color
  119.     function validateData(index, color) {
  120.         let tempState = Calc.getState();
  121.  
  122.         if (isNaN(index))
  123.             return false;
  124.         if (index < 0 || index >= tempState.expressions.list.length)
  125.             return false;
  126.  
  127.         let item = tempState.expressions.list[index];
  128.  
  129.         if (item.hasOwnProperty('type')) {
  130.             if (item.type === 'expression') {
  131.                 let hexColRegex = /^#[A-Fa-f0-9]{6}$|^#[A-Fa-f0-9]{3}$/m;
  132.                 if (hexColRegex.test(color)) {
  133.                     return true;
  134.                 }
  135.             } else {
  136.                 console.log(`no need to change colors of items of ${item.type} type`);
  137.             }
  138.         }
  139.  
  140.         return false;
  141.     }
  142.  
  143.     //parses a custom made JSON object into DOM objects with their properties set up
  144.     function insertNodes(jsonTree, parentNode, outControls) {
  145.         for (let item of jsonTree.controls) {
  146.             outControls[item.id] = document.createElement(item.name);
  147.             outControls[item.id].setAttribute('id', item.id);
  148.             parentNode.appendChild(outControls[item.id]);
  149.  
  150.             if (item.hasOwnProperty('classes')) {
  151.                 item.classes.forEach(elem => outControls[item.id].classList.add(elem));
  152.             }
  153.  
  154.             if (item.hasOwnProperty('styles')) {
  155.                 Object.assign(outControls[item.id].style, item.styles);
  156.             }
  157.  
  158.             if (item.hasOwnProperty('attributes')) {
  159.                 item.attributes.forEach(elem => outControls[item.id].setAttribute(elem.name, elem.value));
  160.             }
  161.  
  162.             if (item.hasOwnProperty('textContent')) {
  163.                 outControls[item.id].innerHTML = item.textContent;
  164.             }
  165.  
  166.             if (item.hasOwnProperty('controls')) {
  167.                 insertNodes(item, outControls[item.id], outControls);
  168.             }
  169.  
  170.         }
  171.     }
  172.  
  173.     /***************************************************************************/
  174.     // ELEMENT SEEKING FUNCTIONS
  175.  
  176.     // calls provided callback whenever an expression menu in Desmos is deployed
  177.     function hookMenu(callback) {
  178.         // initializes observer
  179.         let menuObserver = new MutationObserver( obsRec => {
  180.             let idx = 0;
  181.             let menuElem;
  182.             let isFound = false;
  183.            
  184.             const ITEM_TABLE = 0, ITEM_EXPRESSION = 1;
  185.            
  186.             // repeats search until sought item is found in the list of addedNodes
  187.             do {
  188.                 if (obsRec[idx].addedNodes.length > 0) {
  189.                     obsRec[idx].addedNodes.forEach((item, i) => {
  190.                         if (typeof item.getElementsByClassName === 'function') {
  191.                             let menuColumn = item.getElementsByClassName('dcg-options-menu-column-left');
  192.                            
  193.                             if (menuColumn.length !== 0) {
  194.                                 menuElem = menuColumn[0].parentNode;
  195.                                 isFound = true;
  196.                             }
  197.                            
  198.                         }
  199.                     });
  200.                    
  201.                 }
  202.                 ++idx;
  203.             } while (idx < obsRec.length && !isFound);
  204.            
  205.             let expItem = {};
  206.            
  207.             // if an item was found then finds appropriate values for expItem
  208.             if (isFound) {
  209.                 let expElem = { length: 0 };
  210.                 let expType, expId, expCell;
  211.                
  212.                 let typeIdx = -1;
  213.                 // list of queries to determine the type of the element (table/regular)
  214.                 const seekList = ['.dcg-expressionitem.dcg-expressiontable.dcg-depressed,.dcg-expressionitem.dcg-expressiontable.dcg-hovered', '.dcg-expressionitem.dcg-depressed,.dcg-expressionitem.dcg-hovered'];
  215.                
  216.                 // traverse seekList to find fitting element container
  217.                 seekList.forEach((query, i) => {
  218.                     if (expElem.length === 0) {
  219.                         expElem = document.querySelectorAll(query);
  220.                        
  221.                         typeIdx = i;
  222.                     }
  223.                    
  224.                 });
  225.                
  226.                 // furnishes expItem depending on the type of the expression
  227.                 switch (typeIdx) {
  228.                     case ITEM_TABLE:
  229.                         expType = 'table';
  230.                         expId = expElem[0].getAttribute('expr-id');
  231.                         expCell = seekAttribute(expElem[0], '.dcg-cell.dcg-depressed,.dcg-cell.dcg-hovered', 'index')[0];
  232.                        
  233.                         expItem = {
  234.                             type: expType,
  235.                             id: expId.toString(),
  236.                             colIndex: expCell
  237.                         }
  238.                         break;
  239.                     case ITEM_EXPRESSION:
  240.                         expType = 'expression';
  241.                         expId = expElem[0].getAttribute('expr-id');
  242.                        
  243.                         expItem = {
  244.                             type: expType,
  245.                             id: expId.toString()
  246.                         }
  247.                         break;
  248.                     default:
  249.                        
  250.                 }
  251.                
  252.             }
  253.            
  254.             callback(menuElem, expItem, isFound);
  255.             //console.table(watchList);
  256.         });
  257.        
  258.         let menuContainer = findOptionsMenu();
  259.        
  260.         if (menuContainer !== null) {  
  261.             menuObserver.observe(menuContainer, {
  262.                 childList: true
  263.             });
  264.            
  265.         } else {
  266.             console.log('couldn\'t find menu container');
  267.            
  268.         }
  269.        
  270.     }
  271.  
  272.     function getCurrentIndex () {
  273.         let calcExpressions = Calc.getExpressions();
  274.         return calcExpressions.findIndex(elem => elem.id === currMenuItem.id);
  275.     }
  276.  
  277.     function getCurrentColor() {
  278.         let calcExpressions = Calc.getExpressions();
  279.         let index = calcExpressions.findIndex(elem => elem.id === currMenuItem.id);
  280.        
  281.         if (currMenuItem.type === 'expression') {
  282.             return calcExpressions[index].color;
  283.            
  284.         } else if (currMenuItem.type === 'table') {
  285.             let expr = Calc.getExpressions();
  286.            
  287.             return calcExpressions[index].columns[currMenuItem.colIndex].color;
  288.         }
  289.        
  290.     }
  291.  
  292.     // finds element that contains the color menu in Desmos
  293.     function findOptionsMenu() {
  294.        
  295.         let targetChild = document.getElementsByClassName('dcg-exppanel-outer');
  296.        
  297.         if (targetChild.length == 1) {
  298.             return targetChild[0].parentNode;
  299.            
  300.         } else {
  301.             return null;
  302.            
  303.         }
  304.     }
  305.  
  306.     // performs a css query on an element and aggregates all found values of a specified attribute
  307.     function seekAttribute(parent, query, attName) {
  308.         let output = [];
  309.         let nodes = parent.querySelectorAll(query);
  310.        
  311.         if (nodes.length > 0) {
  312.             nodes.forEach((node, i) => {
  313.                 if (typeof node.getAttributeNames === 'function') {
  314.                     if (node.getAttributeNames().indexOf(attName)) {
  315.                         output.push(node.getAttribute(attName));
  316.                     }
  317.                 }
  318.             });
  319.            
  320.         }
  321.        
  322.         return output;
  323.     }
  324. })()
Add Comment
Please, Sign In to add comment