SlimRunner

v1-0-0-DesmosColorPicker

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