CGC_Codes

utils.js

Jan 30th, 2017
157
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import flow from 'lodash/flow';
  2. import * as challengeTypes from '../../utils/challengeTypes';
  3. import protect from '../../utils/empty-protector';
  4. import { decodeScriptTags } from '../../../utils/encode-decode';
  5.  
  6. // determine the component to view for each challenge
  7. export const viewTypes = {
  8.   [ challengeTypes.html ]: 'classic',
  9.   [ challengeTypes.js ]: 'classic',
  10.   [ challengeTypes.bonfire ]: 'classic',
  11.   [ challengeTypes.frontEndProject ]: 'project',
  12.   [ challengeTypes.backEndProject ]: 'project',
  13.   // might not be used anymore
  14.   [ challengeTypes.simpleProject ]: 'project',
  15.   // formally hikes
  16.   [ challengeTypes.video ]: 'video',
  17.   [ challengeTypes.step ]: 'step',
  18.   backend: 'backend'
  19. };
  20.  
  21. // determine the type of submit function to use for the challenge on completion
  22. export const submitTypes = {
  23.   [ challengeTypes.html ]: 'tests',
  24.   [ challengeTypes.js ]: 'tests',
  25.   [ challengeTypes.bonfire ]: 'tests',
  26.   // requires just a button press
  27.   [ challengeTypes.simpleProject ]: 'project.simple',
  28.   // requires just a single url
  29.   // like codepen.com/my-project
  30.   [ challengeTypes.frontEndProject ]: 'project.frontEnd',
  31.   // requires two urls
  32.   // a hosted URL where the app is running live
  33.   // project code url like GitHub
  34.   [ challengeTypes.backEndProject ]: 'project.backEnd',
  35.   // formally hikes
  36.   [ challengeTypes.video ]: 'video',
  37.   [ challengeTypes.step ]: 'step',
  38.   backend: 'backend'
  39. };
  40.  
  41. // determines if a line in a challenge description
  42. // has html that should be rendered
  43. export const descriptionRegex = /\<blockquote|\<ol|\<h4|\<table/;
  44.  
  45. export function arrayToString(seedData = ['']) {
  46.   seedData = Array.isArray(seedData) ? seedData : [seedData];
  47.   return seedData.reduce((seed, line) => '' + seed + line + '\n', '\n');
  48. }
  49.  
  50. export function buildSeed({ challengeSeed = [] } = {}) {
  51.   return flow(
  52.     arrayToString,
  53.     decodeScriptTags
  54.   )(challengeSeed);
  55. }
  56.  
  57. const pathsMap = {
  58.   [ challengeTypes.html ]: 'html',
  59.   [ challengeTypes.js ]: 'js',
  60.   [ challengeTypes.bonfire ]: 'js'
  61. };
  62.  
  63. export function getPreFile({ challengeType }) {
  64.   return {
  65.     name: 'index',
  66.     ext: pathsMap[challengeType] || 'html',
  67.     key: getFileKey({ challengeType })
  68.   };
  69. }
  70.  
  71. export function getFileKey({ challengeType }) {
  72.   return 'index' + (pathsMap[challengeType] || 'html');
  73. }
  74.  
  75. export function createTests({ tests = [] }) {
  76.   return tests
  77.     .map(test => {
  78.       if (typeof test === 'string') {
  79.         return {
  80.           text: ('' + test).split('message: ').pop().replace(/\'\);/g, ''),
  81.           testString: test
  82.         };
  83.       }
  84.       return test;
  85.     });
  86. }
  87.  
  88. function logReplacer(value) {
  89.   if (Array.isArray(value)) {
  90.     const replaced = value.map(logReplacer);
  91.     return '[' + replaced.join(', ') + ']';
  92.   }
  93.   if (typeof value === 'string' && !value.startsWith('//')) {
  94.     return '"' + value + '"';
  95.   }
  96.   if (typeof value === 'number' && isNaN(value)) {
  97.     return value.toString();
  98.   }
  99.   if (typeof value === 'undefined') {
  100.     return 'undefined';
  101.   }
  102.   if (value === null) {
  103.     return 'null';
  104.   }
  105.   if (typeof value === 'function') {
  106.     return value.name;
  107.   }
  108.   if (typeof value === 'object') {
  109.     return JSON.stringify(value, null, 2);
  110.   }
  111.  
  112.   return value;
  113. }
  114.  
  115. export function loggerToStr(args) {
  116.   args = Array.isArray(args) ? args : [args];
  117.   return args
  118.     .map(logReplacer)
  119.     .reduce((str, arg) => str + arg + '\n', '');
  120. }
  121.  
  122. export function getNextChallenge(
  123.   current,
  124.   entities,
  125.   {
  126.     isDev = false,
  127.     skip = 0
  128.   } = {}
  129. ) {
  130.   const { challenge: challengeMap, block: blockMap } = entities;
  131.   // find current challenge
  132.   // find current block
  133.   // find next challenge in block
  134.   const currentChallenge = challengeMap[current];
  135.   if (!currentChallenge) {
  136.     return null;
  137.   }
  138.   const block = blockMap[currentChallenge.block];
  139.   const index = block.challenges.indexOf(currentChallenge.dashedName);
  140.   // use next challenge name to find challenge in challenge map
  141.   const nextChallenge = challengeMap[
  142.     // grab next challenge name in current block
  143.     // skip is used to skip isComingSoon challenges
  144.     block.challenges[ index + 1 + skip ]
  145.   ];
  146.   if (
  147.     !isDev &&
  148.     nextChallenge &&
  149.     (nextChallenge.isComingSoon || nextChallenge.isBeta)
  150.   ) {
  151.     // if we find a next challenge and it is a coming soon
  152.     // recur with plus one to skip this challenge
  153.     return getNextChallenge(current, entities, { isDev, skip: skip + 1 });
  154.   }
  155.   return nextChallenge;
  156. }
  157.  
  158. export function getFirstChallengeOfNextBlock(
  159.   current,
  160.   entities,
  161.   {
  162.     isDev = false,
  163.     skip = 0
  164.   } = {}
  165. ) {
  166.   const {
  167.     challenge: challengeMap,
  168.     block: blockMap,
  169.     superBlock: SuperBlockMap
  170.   } = entities;
  171.   const currentChallenge = challengeMap[current];
  172.   if (!currentChallenge) {
  173.     return null;
  174.   }
  175.   const block = blockMap[currentChallenge.block];
  176.   if (!block) {
  177.     return null;
  178.   }
  179.   const superBlock = SuperBlockMap[block.superBlock];
  180.   if (!superBlock) {
  181.     return null;
  182.   }
  183.   // find index of current block
  184.   const index = superBlock.blocks.indexOf(block.dashedName);
  185.  
  186.   // find next block name
  187.   // and pull block object from block map
  188.   const newBlock = blockMap[
  189.     superBlock.blocks[ index + 1 + skip ]
  190.   ];
  191.   if (!newBlock) {
  192.     return null;
  193.   }
  194.   // grab first challenge from next block
  195.   const nextChallenge = challengeMap[newBlock.challenges[0]];
  196.   if (isDev || !nextChallenge || !nextChallenge.isComingSoon) {
  197.     return nextChallenge;
  198.   }
  199.   // if first challenge is coming soon, find next challenge here
  200.   const nextChallenge2 = getNextChallenge(
  201.     nextChallenge.dashedName,
  202.     entities,
  203.     { isDev }
  204.   );
  205.   if (nextChallenge2) {
  206.     return nextChallenge2;
  207.   }
  208.   // whole block is coming soon
  209.   // skip this block
  210.   return getFirstChallengeOfNextBlock(
  211.     current,
  212.     entities,
  213.     { isDev, skip: skip + 1 }
  214.   );
  215. }
  216.  
  217. export function getFirstChallengeOfNextSuperBlock(
  218.   current,
  219.   entities,
  220.   superBlocks,
  221.   {
  222.     isDev = false,
  223.     skip = 0
  224.   } = {}
  225. ) {
  226.   const {
  227.     challenge: challengeMap,
  228.     block: blockMap,
  229.     superBlock: SuperBlockMap
  230.   } = entities;
  231.   const currentChallenge = challengeMap[current];
  232.   if (!currentChallenge) {
  233.     return null;
  234.   }
  235.   const block = blockMap[currentChallenge.block];
  236.   if (!block) {
  237.     return null;
  238.   }
  239.   const superBlock = SuperBlockMap[block.superBlock];
  240.   if (!superBlock) {
  241.     return null;
  242.   }
  243.   const index = superBlocks.indexOf(superBlock.dashedName);
  244.   const newSuperBlock = SuperBlockMap[superBlocks[ index + 1 + skip]];
  245.   if (!newSuperBlock) {
  246.     return null;
  247.   }
  248.   const newBlock = blockMap[
  249.     newSuperBlock.blocks[ 0 ]
  250.   ];
  251.   if (!newBlock) {
  252.     return null;
  253.   }
  254.   const nextChallenge = challengeMap[newBlock.challenges[0]];
  255.   if (isDev || !nextChallenge || !nextChallenge.isComingSoon) {
  256.     return nextChallenge;
  257.   }
  258.   // coming soon challenge, grab next
  259.   // non coming soon challenge in same block instead
  260.   const nextChallengeInBlock = getNextChallenge(
  261.     nextChallenge.dashedName,
  262.     entities,
  263.     { isDev }
  264.   );
  265.   if (nextChallengeInBlock) {
  266.     return nextChallengeInBlock;
  267.   }
  268.   // whole block is coming soon
  269.   // grab first challenge in next block in newSuperBlock instead
  270.   const challengeInNextBlock = getFirstChallengeOfNextBlock(
  271.     nextChallenge.dashedName,
  272.     entities,
  273.     { isDev }
  274.   );
  275.  
  276.   if (challengeInNextBlock) {
  277.     return challengeInNextBlock;
  278.   }
  279.   // whole super block is coming soon
  280.   // skip this super block
  281.   return getFirstChallengeOfNextSuperBlock(
  282.     current,
  283.     entities,
  284.     superBlocks,
  285.     { isDev, skip: skip + 1 }
  286.   );
  287. }
  288.  
  289. export function getCurrentBlockName(current, entities) {
  290.   const { challenge: challengeMap } = entities;
  291.   const challenge = challengeMap[current];
  292.   return challenge.block;
  293. }
  294.  
  295. export function getCurrentSuperBlockName(current, entities) {
  296.   const { challenge: challengeMap, block: blockMap } = entities;
  297.   const challenge = challengeMap[current];
  298.   const block = blockMap[challenge.block];
  299.   return block.superBlock;
  300. }
  301.  
  302. // gets new mouse position
  303. // getMouse(
  304. //   e: MouseEvent|TouchEvent,
  305. //   [ dx: Number, dy: Number ]
  306. // ) => [ Number, Number ]
  307. export function getMouse(e, [dx, dy]) {
  308.   let { pageX, pageY, touches, changedTouches } = e;
  309.  
  310.   // touches can be empty on touchend
  311.   if (touches || changedTouches) {
  312.     e.preventDefault();
  313.     // these re-assigns the values of pageX, pageY from touches
  314.     ({ pageX, pageY } = touches[0] || changedTouches[0]);
  315.   }
  316.  
  317.   return [pageX - dx, pageY - dy];
  318. }
  319.  
  320. export function filterComingSoonBetaChallenge(
  321.   isDev = false,
  322.   { isComingSoon, isBeta }
  323. ) {
  324.   return !(isComingSoon || isBeta) ||
  325.     isDev;
  326. }
  327.  
  328. export function filterComingSoonBetaFromEntities(
  329.   { challenge: challengeMap, ...rest },
  330.   isDev = false
  331. ) {
  332.   const filter = filterComingSoonBetaChallenge.bind(null, isDev);
  333.   return {
  334.     ...rest,
  335.     challenge: Object.keys(challengeMap)
  336.       .map(dashedName => challengeMap[dashedName])
  337.       .filter(filter)
  338.       .reduce((challengeMap, challenge) => {
  339.         challengeMap[challenge.dashedName] = challenge;
  340.         return challengeMap;
  341.       }, {})
  342.   };
  343. }
  344.  
  345. export function searchableChallengeTitles({ challenge: challengeMap } = {}) {
  346.   return Object.keys(challengeMap)
  347.     .map(dashedName => challengeMap[dashedName])
  348.     .reduce((accu, current) => {
  349.         accu[current.dashedName] = current.title;
  350.         return accu;
  351.       }
  352.     , {});
  353. }
  354.  
  355. // interface Node {
  356. //   isHidden: Boolean,
  357. //   children: Void|[ ...Node ],
  358. //   isOpen?: Boolean
  359. // }
  360. //
  361. // interface MapUi
  362. // {
  363. //   children: [...{
  364. //     name: (superBlock: String),
  365. //     isOpen: Boolean,
  366. //     isHidden: Boolean,
  367. //     children: [...{
  368. //       name: (blockName: String),
  369. //       isOpen: Boolean,
  370. //       isHidden: Boolean,
  371. //       children: [...{
  372. //         name: (challengeName: String),
  373. //         isHidden: Boolean
  374. //       }]
  375. //     }]
  376. //   }]
  377. // }
  378. export function createMapUi(
  379.   { superBlock: superBlockMap, block: blockMap } = {},
  380.   superBlocks,
  381.   searchNameMap
  382. ) {
  383.   if (!superBlocks || !superBlockMap || !blockMap) {
  384.     return {};
  385.   }
  386.   return {
  387.     children: superBlocks.map(superBlock => {
  388.       return {
  389.         name: superBlock,
  390.         isOpen: true,
  391.         isHidden: false,
  392.         children: protect(superBlockMap[superBlock]).blocks.map(block => {
  393.           return {
  394.             name: block,
  395.             isOpen: true,
  396.             isHidden: false,
  397.             children: protect(blockMap[block]).challenges.map(challenge => {
  398.               return {
  399.                 name: challenge,
  400.                 title: searchNameMap[challenge],
  401.                 isHidden: false,
  402.                 children: null
  403.               };
  404.             })
  405.           };
  406.         })
  407.       };
  408.     })
  409.   };
  410. }
  411.  
  412. // synchronise
  413. // traverseMapUi(
  414. //   tree: MapUi|Node,
  415. //   update: ((MapUi|Node) => MapUi|Node)
  416. // ) => MapUi|Node
  417. export function traverseMapUi(tree, update) {
  418.   let childrenChanged;
  419.   if (!Array.isArray(tree.children)) {
  420.     return update(tree);
  421.   }
  422.   const newChildren = tree.children.map(node => {
  423.     const newNode = traverseMapUi(node, update);
  424.     if (!childrenChanged && newNode !== node) {
  425.       childrenChanged = true;
  426.     }
  427.     return newNode;
  428.   });
  429.   if (childrenChanged) {
  430.     tree = {
  431.       ...tree,
  432.       children: newChildren
  433.     };
  434.   }
  435.   return update(tree);
  436. }
  437.  
  438. // synchronise
  439. // getNode(tree: MapUi, name: String) => MapUi
  440. export function getNode(tree, name) {
  441.   let node;
  442.   traverseMapUi(tree, thisNode => {
  443.     if (thisNode.name === name) {
  444.       node = thisNode;
  445.     }
  446.     return thisNode;
  447.   });
  448.   return node;
  449. }
  450.  
  451. // synchronise
  452. // updateSingelNode(
  453. //   tree: MapUi,
  454. //   name: String,
  455. //   update(MapUi|Node) => MapUi|Node
  456. // ) => MapUi
  457. export function updateSingleNode(tree, name, update) {
  458.   return traverseMapUi(tree, node => {
  459.     if (name !== node.name) {
  460.       return node;
  461.     }
  462.     return update(node);
  463.   });
  464. }
  465.  
  466. // synchronise
  467. // toggleThisPanel(tree: MapUi, name: String) => MapUi
  468. export function toggleThisPanel(tree, name) {
  469.   return updateSingleNode(tree, name, node => {
  470.     return {
  471.       ...node,
  472.       isOpen: !node.isOpen
  473.     };
  474.   });
  475. }
  476.  
  477. // toggleAllPanels(tree: MapUi, isOpen: Boolean = false ) => MapUi
  478. export function toggleAllPanels(tree, isOpen = false) {
  479.   return traverseMapUi(tree, node => {
  480.     if (!Array.isArray(node.children) || node.isOpen === isOpen) {
  481.       return node;
  482.     }
  483.     return {
  484.       ...node,
  485.       isOpen
  486.     };
  487.   });
  488. }
  489.  
  490. // collapseAllPanels(tree: MapUi) => MapUi
  491. export function collapseAllPanels(tree) {
  492.   return toggleAllPanels(tree);
  493. }
  494.  
  495. // expandAllPanels(tree: MapUi) => MapUi
  496. export function expandAllPanels(tree) {
  497.   return toggleAllPanels(tree, true);
  498. }
  499.  
  500. // applyFilterToMap(tree: MapUi, filterRegex: RegExp) => MapUi
  501. export function applyFilterToMap(tree, filterRegex) {
  502.   return traverseMapUi(
  503.     tree,
  504.     node => {
  505.       // no children indicates a challenge node
  506.       // if leaf (challenge) then test if regex is a match
  507.       if (!Array.isArray(node.children)) {
  508.         // does challenge name meet filter criteria?
  509.         if (filterRegex.test(node.title)) {
  510.           // is challenge currently hidden?
  511.           if (node.isHidden) {
  512.             // unhide challenge, it matches
  513.             return {
  514.               ...node,
  515.               isHidden: false
  516.             };
  517.           }
  518.         } else if (!node.isHidden) {
  519.           return {
  520.             ...node,
  521.             isHidden: true
  522.           };
  523.         }
  524.         return node;
  525.       }
  526.       // if not leaf node (challenge) then
  527.       // test to see if all its children are hidden
  528.       if (node.children.every(node => node.isHidden)) {
  529.         if (node.isHidden) {
  530.           return node;
  531.         }
  532.         return {
  533.           ...node,
  534.           isHidden: true
  535.         };
  536.       } else if (node.isHidden) {
  537.         return {
  538.           ...node,
  539.           isHidden: false
  540.         };
  541.       }
  542.       // nothing has changed
  543.       return node;
  544.     }
  545.   );
  546. }
  547.  
  548. // unfilterMapUi(tree: MapUi) => MapUi
  549. export function unfilterMapUi(tree) {
  550.   return traverseMapUi(
  551.     tree,
  552.     node => {
  553.       if (!node.isHidden) {
  554.         return node;
  555.       }
  556.       return {
  557.         ...node,
  558.         isHidden: false
  559.       };
  560.     }
  561.   );
  562. }
Advertisement
Add Comment
Please, Sign In to add comment