├── .eslintrc ├── .flowconfig ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RESOURCES.md ├── flow-typed └── npm │ ├── axios_v0.17.x.js │ ├── babel-standalone_vx.x.x.js │ ├── chalk_v2.x.x.js │ ├── codemirror_vx.x.x.js │ ├── feather-icons_vx.x.x.js │ ├── flow-bin_v0.x.x.js │ ├── flow-typed_vx.x.x.js │ ├── jshint_vx.x.x.js │ ├── lodash_v4.x.x.js │ ├── loop-protect_vx.x.x.js │ ├── prop-types_v15.x.x.js │ ├── react-codemirror2_vx.x.x.js │ ├── react-feather_vx.x.x.js │ ├── react-redux_v5.x.x.js │ ├── react-scripts_vx.x.x.js │ ├── react-toastify_v3.2.x.js │ ├── react-tooltip_vx.x.x.js │ ├── react-transition-group_v2.x.x.js │ ├── react-transition-group_vx.x.x.js │ ├── redux-devtools-extension_v2.x.x.js │ ├── redux_v3.x.x.js │ ├── shortid_v2.2.x.js │ └── validator_vx.x.x.js ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.js ├── __tests__ │ ├── AlgorithmChallenges.test.js │ ├── DataStructures.test.js │ └── SortingAlgorithms.test.js ├── actions │ ├── console.js │ ├── editor.js │ ├── menu.js │ ├── modal.js │ ├── panes.js │ └── theme.js ├── assets │ ├── __ChallengeTemplate__.js │ ├── __TestsTemplate__.js │ ├── codeRef.js │ ├── seed │ │ ├── FreeCode.js │ │ ├── algorithms │ │ │ ├── AnagramPalindrome.js │ │ │ ├── BubbleSort.js │ │ │ ├── BucketSort.js │ │ │ ├── FlattenAnArray.js │ │ │ ├── GenerateCheckerboard.js │ │ │ ├── HeapSort.js │ │ │ ├── InsertionSort.js │ │ │ ├── LongestCommonPrefix.js │ │ │ ├── Mergesort.js │ │ │ ├── NoTwoConsecutiveChars.js │ │ │ ├── Quicksort.js │ │ │ ├── ReverseAString.js │ │ │ ├── ReverseVowels.js │ │ │ ├── SelectionSort.js │ │ │ ├── SortingBenchmarks.js │ │ │ └── SumAllPrimes.js │ │ ├── data-structures │ │ │ ├── BinarySearchTree.js │ │ │ ├── CircularDoublyLinkedList.js │ │ │ ├── DoublyLinkedList.js │ │ │ ├── Graph.js │ │ │ ├── HashTable.js │ │ │ ├── LinkedList.js │ │ │ ├── MaxHeap.js │ │ │ ├── PriorityQueue.js │ │ │ ├── Queue.js │ │ │ └── Stack.js │ │ └── welcome.js │ ├── testRef.js │ └── tests │ │ ├── algorithms │ │ ├── AnagramPalindrome.js │ │ ├── BubbleSort.js │ │ ├── BucketSort.js │ │ ├── FlattenAnArray.js │ │ ├── GenerateCheckerboard.js │ │ ├── HeapSort.js │ │ ├── InsertionSort.js │ │ ├── LongestCommonPrefix.js │ │ ├── Mergesort.js │ │ ├── NoTwoConsecutiveChars.js │ │ ├── Quicksort.js │ │ ├── ReverseAString.js │ │ ├── ReverseVowels.js │ │ ├── SelectionSort.js │ │ └── SumAllPrimes.js │ │ └── data-structures │ │ ├── BinarySearchTree.js │ │ ├── CircularDoublyLinkedList.js │ │ ├── DoublyLinkedList.js │ │ ├── Graph.js │ │ ├── HashTable.js │ │ ├── LinkedList.js │ │ ├── MaxHeap.js │ │ ├── PriorityQueue.js │ │ ├── Queue.js │ │ └── Stack.js ├── components │ ├── ActionMenu.js │ ├── CodeMirrorRenderer.js │ ├── Controls.js │ ├── Modal.js │ ├── modals │ │ ├── AnnouncementModal.js │ │ ├── BindingsModal.js │ │ ├── ConfirmModal.js │ │ ├── ResourcesModal.js │ │ └── ThemeModal.js │ ├── sidebar │ │ ├── Console.js │ │ ├── Menu.js │ │ ├── MenuMap.js │ │ └── ReplsMap.js │ └── utils │ │ ├── BounceTransition.js │ │ ├── Divider.js │ │ ├── ErrorBoundary.js │ │ ├── Fader.js │ │ ├── MenuButtons.js │ │ └── Pane.js ├── index.js ├── reducers │ ├── console.js │ ├── editor │ │ ├── children │ │ │ ├── challenges.js │ │ │ ├── repls.js │ │ │ └── solutions.js │ │ ├── editor.js │ │ └── utils │ │ │ ├── createOrderKey.js │ │ │ ├── initializationUtils.js │ │ │ ├── populateCodeStore.js │ │ │ └── updateCodeStore.js │ ├── menu.js │ ├── modal.js │ ├── panes.js │ ├── rootReducer.js │ └── theme.js ├── store.js ├── styles │ ├── app.css │ ├── codemirror.css │ ├── console.css │ ├── controls.css │ ├── index.css │ ├── menu.css │ ├── modal.css │ └── themes │ │ ├── __index__.css │ │ ├── abcdef.css │ │ ├── base16-dark.css │ │ ├── base16-light.css │ │ ├── cobalt.css │ │ ├── eclipse.css │ │ ├── icecoder.css │ │ ├── lesser-dark.css │ │ ├── material.css │ │ ├── monokai.css │ │ ├── neat.css │ │ ├── panda-syntax.css │ │ ├── paraiso-light.css │ │ ├── tomorrow-night-eighties.css │ │ └── twilight.css ├── types │ ├── Actions.js │ ├── Reducers.js │ └── State.js └── utils │ ├── base64.js │ ├── createProxyConsole.js │ ├── editorConfig.js │ ├── localStorageKeys.js │ ├── onDrag.js │ ├── regexp.js │ ├── registerServiceWorker.js │ ├── simpleDrag.js │ ├── test │ ├── app │ │ ├── create-jest-test.js │ │ ├── jest-test-scripts.js │ │ └── jest-test-utils.js │ ├── challenge │ │ ├── eval-code-run-tests.js │ │ ├── execute-tests.js │ │ ├── loop-protect.js │ │ └── testReport.js │ └── common │ │ └── is-test-disabled.js │ └── toggleControlComments.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | 11 | [strict] 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | # Visual Studio Code 21 | .vs/* 22 | .vs/** 23 | /.vs 24 | /.vs/* 25 | /.vs/** 26 | 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false, 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Peter Weinberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /flow-typed/npm/babel-standalone_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: e19f406137937ed343c31c29e21b9b30 2 | // flow-typed version: <>/babel-standalone_v^6.26.0/flow_v0.66.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'babel-standalone' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'babel-standalone' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'babel-standalone/babel' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'babel-standalone/babel.min' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'babel-standalone/src/babel-package-shim' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'babel-standalone/src/babili' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'babel-standalone/src/index' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'babel-standalone/src/transformScriptTags' { 46 | declare module.exports: any; 47 | } 48 | 49 | // Filename aliases 50 | declare module 'babel-standalone/babel.js' { 51 | declare module.exports: $Exports<'babel-standalone/babel'>; 52 | } 53 | declare module 'babel-standalone/babel.min.js' { 54 | declare module.exports: $Exports<'babel-standalone/babel.min'>; 55 | } 56 | declare module 'babel-standalone/src/babel-package-shim.js' { 57 | declare module.exports: $Exports<'babel-standalone/src/babel-package-shim'>; 58 | } 59 | declare module 'babel-standalone/src/babili.js' { 60 | declare module.exports: $Exports<'babel-standalone/src/babili'>; 61 | } 62 | declare module 'babel-standalone/src/index.js' { 63 | declare module.exports: $Exports<'babel-standalone/src/index'>; 64 | } 65 | declare module 'babel-standalone/src/transformScriptTags.js' { 66 | declare module.exports: $Exports<'babel-standalone/src/transformScriptTags'>; 67 | } 68 | -------------------------------------------------------------------------------- /flow-typed/npm/chalk_v2.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: fa51178772ad1f35158cb4238bc3f1eb 2 | // flow-typed version: da30fe6876/chalk_v2.x.x/flow_>=v0.25.x 3 | 4 | type $npm$chalk$StyleElement = { 5 | open: string, 6 | close: string 7 | }; 8 | 9 | type $npm$chalk$Chain = $npm$chalk$Style & ((...text: any[]) => string); 10 | 11 | type $npm$chalk$Style = { 12 | // General 13 | reset: $npm$chalk$Chain, 14 | bold: $npm$chalk$Chain, 15 | dim: $npm$chalk$Chain, 16 | italic: $npm$chalk$Chain, 17 | underline: $npm$chalk$Chain, 18 | inverse: $npm$chalk$Chain, 19 | strikethrough: $npm$chalk$Chain, 20 | 21 | // Text colors 22 | black: $npm$chalk$Chain, 23 | red: $npm$chalk$Chain, 24 | redBright: $npm$chalk$Chain, 25 | green: $npm$chalk$Chain, 26 | greenBright: $npm$chalk$Chain, 27 | yellow: $npm$chalk$Chain, 28 | yellowBright: $npm$chalk$Chain, 29 | blue: $npm$chalk$Chain, 30 | blueBright: $npm$chalk$Chain, 31 | magenta: $npm$chalk$Chain, 32 | magentaBright: $npm$chalk$Chain, 33 | cyan: $npm$chalk$Chain, 34 | cyanBright: $npm$chalk$Chain, 35 | white: $npm$chalk$Chain, 36 | whiteBright: $npm$chalk$Chain, 37 | gray: $npm$chalk$Chain, 38 | grey: $npm$chalk$Chain, 39 | 40 | // Background colors 41 | bgBlack: $npm$chalk$Chain, 42 | bgBlackBright: $npm$chalk$Chain, 43 | bgRed: $npm$chalk$Chain, 44 | bgRedBright: $npm$chalk$Chain, 45 | bgGreen: $npm$chalk$Chain, 46 | bgGreenBright: $npm$chalk$Chain, 47 | bgYellow: $npm$chalk$Chain, 48 | bgYellowBright: $npm$chalk$Chain, 49 | bgBlue: $npm$chalk$Chain, 50 | bgBlueBright: $npm$chalk$Chain, 51 | bgMagenta: $npm$chalk$Chain, 52 | bgMagentaBright: $npm$chalk$Chain, 53 | bgCyan: $npm$chalk$Chain, 54 | bgCyanBright: $npm$chalk$Chain, 55 | bgWhite: $npm$chalk$Chain, 56 | bgWhiteBright: $npm$chalk$Chain 57 | }; 58 | 59 | declare module "chalk" { 60 | declare var enabled: boolean; 61 | declare var supportsColor: boolean; 62 | 63 | // General 64 | declare var reset: $npm$chalk$Chain; 65 | declare var bold: $npm$chalk$Chain; 66 | declare var dim: $npm$chalk$Chain; 67 | declare var italic: $npm$chalk$Chain; 68 | declare var underline: $npm$chalk$Chain; 69 | declare var inverse: $npm$chalk$Chain; 70 | declare var strikethrough: $npm$chalk$Chain; 71 | 72 | // Text colors 73 | declare var black: $npm$chalk$Chain; 74 | declare var red: $npm$chalk$Chain; 75 | declare var redBright: $npm$chalk$Chain; 76 | declare var green: $npm$chalk$Chain; 77 | declare var greenBright: $npm$chalk$Chain; 78 | declare var yellow: $npm$chalk$Chain; 79 | declare var yellowBright: $npm$chalk$Chain; 80 | declare var blue: $npm$chalk$Chain; 81 | declare var blueBright: $npm$chalk$Chain; 82 | declare var magenta: $npm$chalk$Chain; 83 | declare var magentaBright: $npm$chalk$Chain; 84 | declare var cyan: $npm$chalk$Chain; 85 | declare var cyanBright: $npm$chalk$Chain; 86 | declare var white: $npm$chalk$Chain; 87 | declare var whiteBright: $npm$chalk$Chain; 88 | declare var gray: $npm$chalk$Chain; 89 | declare var grey: $npm$chalk$Chain; 90 | 91 | // Background colors 92 | declare var bgBlack: $npm$chalk$Chain; 93 | declare var bgBlackBright: $npm$chalk$Chain; 94 | declare var bgRed: $npm$chalk$Chain; 95 | declare var bgRedBright: $npm$chalk$Chain; 96 | declare var bgGreen: $npm$chalk$Chain; 97 | declare var bgGreenBright: $npm$chalk$Chain; 98 | declare var bgYellow: $npm$chalk$Chain; 99 | declare var bgYellowBright: $npm$chalk$Chain; 100 | declare var bgBlue: $npm$chalk$Chain; 101 | declare var bgBlueBright: $npm$chalk$Chain; 102 | declare var bgMagenta: $npm$chalk$Chain; 103 | declare var bgMagentaBright: $npm$chalk$Chain; 104 | declare var bgCyan: $npm$chalk$Chain; 105 | declare var bgCyanBright: $npm$chalk$Chain; 106 | declare var bgWhite: $npm$chalk$Chain; 107 | declare var bgWhiteBright: $npm$chalk$Chain; 108 | } 109 | -------------------------------------------------------------------------------- /flow-typed/npm/feather-icons_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 548fe2b2dc1b564478bc967a88c20216 2 | // flow-typed version: <>/feather-icons_v^4.5.0/flow_v0.66.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'feather-icons' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'feather-icons' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'feather-icons/dist/feather' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'feather-icons/dist/feather.min' { 30 | declare module.exports: any; 31 | } 32 | 33 | // Filename aliases 34 | declare module 'feather-icons/dist/feather.js' { 35 | declare module.exports: $Exports<'feather-icons/dist/feather'>; 36 | } 37 | declare module 'feather-icons/dist/feather.min.js' { 38 | declare module.exports: $Exports<'feather-icons/dist/feather.min'>; 39 | } 40 | -------------------------------------------------------------------------------- /flow-typed/npm/flow-bin_v0.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583 2 | // flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x 3 | 4 | declare module "flow-bin" { 5 | declare module.exports: string; 6 | } 7 | -------------------------------------------------------------------------------- /flow-typed/npm/loop-protect_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 72c5a26ca581351c775aca3695df556d 2 | // flow-typed version: <>/loop-protect_v^2.1.6/flow_v0.66.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'loop-protect' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'loop-protect' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'loop-protect/dist/index' { 26 | declare module.exports: any; 27 | } 28 | 29 | // Filename aliases 30 | declare module 'loop-protect/dist/index.js' { 31 | declare module.exports: $Exports<'loop-protect/dist/index'>; 32 | } 33 | -------------------------------------------------------------------------------- /flow-typed/npm/prop-types_v15.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: d9a983bb1ac458a256c31c139047bdbb 2 | // flow-typed version: 927687984d/prop-types_v15.x.x/flow_>=v0.41.x 3 | 4 | type $npm$propTypes$ReactPropsCheckType = ( 5 | props: any, 6 | propName: string, 7 | componentName: string, 8 | href?: string) => ?Error; 9 | 10 | declare module 'prop-types' { 11 | declare var array: React$PropType$Primitive>; 12 | declare var bool: React$PropType$Primitive; 13 | declare var func: React$PropType$Primitive; 14 | declare var number: React$PropType$Primitive; 15 | declare var object: React$PropType$Primitive; 16 | declare var string: React$PropType$Primitive; 17 | declare var symbol: React$PropType$Primitive; 18 | declare var any: React$PropType$Primitive; 19 | declare var arrayOf: React$PropType$ArrayOf; 20 | declare var element: React$PropType$Primitive; /* TODO */ 21 | declare var instanceOf: React$PropType$InstanceOf; 22 | declare var node: React$PropType$Primitive; /* TODO */ 23 | declare var objectOf: React$PropType$ObjectOf; 24 | declare var oneOf: React$PropType$OneOf; 25 | declare var oneOfType: React$PropType$OneOfType; 26 | declare var shape: React$PropType$Shape; 27 | 28 | declare function checkPropTypes( 29 | propTypes: $Subtype<{[_: $Keys]: $npm$propTypes$ReactPropsCheckType}>, 30 | values: V, 31 | location: string, 32 | componentName: string, 33 | getStack: ?(() => ?string) 34 | ) : void; 35 | } 36 | -------------------------------------------------------------------------------- /flow-typed/npm/react-codemirror2_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 60c33c3bc9fde5118084ec9a22e5eaf8 2 | // flow-typed version: <>/react-codemirror2_v^3.0.7/flow_v0.66.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'react-codemirror2' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'react-codemirror2' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | 26 | 27 | // Filename aliases 28 | declare module 'react-codemirror2/index' { 29 | declare module.exports: $Exports<'react-codemirror2'>; 30 | } 31 | declare module 'react-codemirror2/index.js' { 32 | declare module.exports: $Exports<'react-codemirror2'>; 33 | } 34 | -------------------------------------------------------------------------------- /flow-typed/npm/react-redux_v5.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: a2c406bd25fca4586c361574e555202d 2 | // flow-typed version: dcd1531faf/react-redux_v5.x.x/flow_>=v0.62.0 3 | 4 | import type { Dispatch, Store } from "redux"; 5 | 6 | declare module "react-redux" { 7 | import type { ComponentType, ElementConfig } from 'react'; 8 | 9 | declare export class Provider extends React$Component<{ 10 | store: Store, 11 | children?: any 12 | }> {} 13 | 14 | declare export function createProvider( 15 | storeKey?: string, 16 | subKey?: string 17 | ): Provider<*, *>; 18 | 19 | /* 20 | 21 | S = State 22 | A = Action 23 | OP = OwnProps 24 | SP = StateProps 25 | DP = DispatchProps 26 | MP = Merge props 27 | MDP = Map dispatch to props object 28 | RSP = Returned state props 29 | RDP = Returned dispatch props 30 | RMP = Returned merge props 31 | Com = React Component 32 | */ 33 | 34 | declare type MapStateToProps = (state: Object, props: SP) => RSP; 35 | 36 | declare type MapDispatchToProps = (dispatch: Dispatch, ownProps: OP) => RDP; 37 | 38 | declare type MergeProps = ( 39 | stateProps: SP, 40 | dispatchProps: DP, 41 | ownProps: MP 42 | ) => RMP; 43 | 44 | declare type ConnectOptions = {| 45 | pure?: boolean, 46 | withRef?: boolean, 47 | areStatesEqual?: (next: S, prev: S) => boolean, 48 | areOwnPropsEqual?: (next: OP, prev: OP) => boolean, 49 | areStatePropsEqual?: (next: RSP, prev: RSP) => boolean, 50 | areMergedPropsEqual?: (next: RMP, prev: RMP) => boolean, 51 | storeKey?: string 52 | |}; 53 | 54 | declare type OmitDispatch = $Diff}>; 55 | 56 | declare export function connect, DP: Object, RSP: Object>( 57 | mapStateToProps: MapStateToProps, 58 | mapDispatchToProps?: null 59 | ): (component: Com) => ComponentType<$Diff>, RSP> & DP>; 60 | 61 | declare export function connect>( 62 | mapStateToProps?: null, 63 | mapDispatchToProps?: null 64 | ): (component: Com) => ComponentType>>; 65 | 66 | declare export function connect, A, DP: Object, SP: Object, RSP: Object, RDP: Object>( 67 | mapStateToProps: MapStateToProps, 68 | mapDispatchToProps: MapDispatchToProps 69 | ): (component: Com) => ComponentType<$Diff<$Diff, RSP>, RDP> & SP & DP>; 70 | 71 | declare export function connect, A, OP: Object, DP: Object,PR: Object>( 72 | mapStateToProps?: null, 73 | mapDispatchToProps: MapDispatchToProps 74 | ): (Com) => ComponentType<$Diff, DP> & OP>; 75 | 76 | declare export function connect, MDP: Object>( 77 | mapStateToProps?: null, 78 | mapDispatchToProps: MDP 79 | ): (component: Com) => ComponentType<$Diff, MDP>>; 80 | 81 | declare export function connect, SP: Object, RSP: Object, MDP: Object>( 82 | mapStateToProps: MapStateToProps, 83 | mapDispatchToPRops: MDP 84 | ): (component: Com) => ComponentType<$Diff<$Diff, RSP>, MDP> & SP>; 85 | 86 | declare export function connect, A, DP: Object, SP: Object, RSP: Object, RDP: Object, MP: Object, RMP: Object>( 87 | mapStateToProps: MapStateToProps, 88 | mapDispatchToProps: ?MapDispatchToProps, 89 | mergeProps: MergeProps 90 | ): (component: Com) => ComponentType<$Diff, RMP> & SP & DP & MP>; 91 | 92 | declare export function connect, A, DP: Object, SP: Object, RSP: Object, RDP: Object, MP: Object, RMP: Object>( 93 | mapStateToProps: ?MapStateToProps, 94 | mapDispatchToProps: ?MapDispatchToProps, 95 | mergeProps: ?MergeProps, 96 | options: ConnectOptions<*, SP & DP & MP, RSP, RMP> 97 | ): (component: Com) => ComponentType<$Diff, RMP> & SP & DP & MP>; 98 | } 99 | -------------------------------------------------------------------------------- /flow-typed/npm/react-toastify_v3.2.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 19637a389d39a4d5b91d38a449cc64c0 2 | // flow-typed version: 4430f7960f/react-toastify_v3.2.x/flow_>=v0.54.x 3 | 4 | declare module "react-toastify" { 5 | declare type ToastType = "info" | "success" | "warning" | "error" | "default"; 6 | 7 | declare type ToastContent = React$Node; 8 | 9 | declare type styleProps = { 10 | width?: string, 11 | colorDefault?: string, 12 | colorInfo?: string, 13 | colorSuccess?: string, 14 | colorWarning?: string, 15 | colorError?: string, 16 | colorProgressDefault?: string, 17 | mobile?: string, 18 | zIndex?: string | number, 19 | TOP_LEFT?: Object, 20 | TOP_CENTER?: Object, 21 | TOP_RIGHT?: Object, 22 | BOTTOM_LEFT?: Object, 23 | BOTTOM_CENTER?: Object, 24 | BOTTOM_RIGHT?: Object 25 | }; 26 | 27 | declare type CommonOptions = { 28 | pauseOnHover?: boolean, 29 | closeOnClick?: boolean, 30 | autoClose?: number | false, 31 | position?: string, 32 | closeButton?: React$Node | false, 33 | progressClassName?: string | Object, 34 | className?: string | Object, 35 | bodyClassName?: string | Object, 36 | hideProgressBar?: boolean, 37 | transition?: any // TODO: Improve 38 | }; 39 | 40 | declare type ToastOptions = CommonOptions & { 41 | onOpen?: () => void, 42 | onClose?: () => void, 43 | type?: ToastType 44 | }; 45 | 46 | declare type UpdateOptions = ToastOptions & { 47 | render?: ToastContent 48 | }; 49 | 50 | declare type ToastContainerProps = CommonOptions & { 51 | newestOnTop?: boolean, 52 | style?: Object, 53 | toastClassName?: string | Object 54 | }; 55 | 56 | declare type Toast = { 57 | (content: ToastContent, options?: ToastOptions): number, 58 | success(content: ToastContent, options?: ToastOptions): number, 59 | info(content: ToastContent, options?: ToastOptions): number, 60 | warn(content: ToastContent, options?: ToastOptions): number, 61 | error(content: ToastContent, options?: ToastOptions): number, 62 | isActive(toastId: number): boolean, 63 | dismiss(toastId?: number): void, 64 | update(toastId: number, options?: UpdateOptions): number, 65 | 66 | TYPE: { 67 | INFO: ToastType, 68 | SUCCESS: ToastType, 69 | WARNING: ToastType, 70 | ERROR: ToastType, 71 | DEFAULT: ToastType 72 | }, 73 | 74 | POSITION: { 75 | TOP_LEFT: string, 76 | TOP_RIGHT: string, 77 | TOP_CENTER: string, 78 | BOTTOM_LEFT: string, 79 | BOTTOM_RIGHT: string, 80 | BOTTOM_CENTER: string 81 | } 82 | }; 83 | 84 | declare export class ToastContainer extends React$Component {} 85 | 86 | declare export function style(props: styleProps): void; 87 | 88 | declare export var toast: Toast; 89 | } 90 | -------------------------------------------------------------------------------- /flow-typed/npm/react-transition-group_v2.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: c25c825628f758a65027ad2a17ed7f2e 2 | // flow-typed version: b6c24caf38/react-transition-group_v2.x.x/flow_>=v0.60.x 3 | 4 | // @flow 5 | 6 | declare module 'react-transition-group' { 7 | declare export type CSSTransitionClassNames = { 8 | appear?: string, 9 | appearActive?: string, 10 | enter?: string, 11 | enterActive?: string, 12 | enterDone?: string, 13 | exit?: string, 14 | exitActive?: string, 15 | exitDone?: string, 16 | }; 17 | 18 | declare export type TransitionStatus = 'entering' | 'entered' | 'exiting' | 'exited' | 'unmounted'; 19 | 20 | declare export type EndHandler = (node: HTMLElement, done: () => void) => void; 21 | declare export type EnterHandler = (node: HTMLElement, isAppearing: boolean) => void; 22 | declare export type ExitHandler = (node: HTMLElement) => void; 23 | 24 | declare type TransitionActions = { 25 | appear?: boolean; 26 | enter?: boolean; 27 | exit?: boolean; 28 | } 29 | 30 | declare type TransitionProps = TransitionActions & { 31 | mountOnEnter?: boolean, 32 | unmountOnExit?: boolean, 33 | onEnter?: EnterHandler, 34 | onEntering?: EnterHandler, 35 | onEntered?: EnterHandler, 36 | onExit?: ExitHandler, 37 | onExiting?: ExitHandler, 38 | onExited?: ExitHandler, 39 | } & ({ 40 | timeout: number | { enter?: number, exit?: number }, 41 | addEndListener?: null, 42 | } | { 43 | timeout?: number | { enter?: number, exit?: number }, 44 | addEndListener: EndHandler, 45 | }) 46 | 47 | declare export class Transition extends React$Component React$Node) | React$Node, 50 | }> {} 51 | 52 | declare export class TransitionGroup extends React$Component React$Node, 56 | }> {} 57 | 58 | declare export class ReplaceTransition extends React$Component {} 62 | 63 | declare export class CSSTransition extends React$Component React$Node) | React$Node, 67 | }> {} 68 | } 69 | -------------------------------------------------------------------------------- /flow-typed/npm/react-transition-group_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: aac624140d49bca34122a2be1f7c451d 2 | // flow-typed version: <>/react-transition-group_v^2.2.1/flow_v0.66.0 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'react-transition-group' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'react-transition-group' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module 'react-transition-group/CSSTransition' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module 'react-transition-group/dist/react-transition-group' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module 'react-transition-group/dist/react-transition-group.min' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module 'react-transition-group/Transition' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module 'react-transition-group/TransitionGroup' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module 'react-transition-group/utils/ChildMapping' { 46 | declare module.exports: any; 47 | } 48 | 49 | declare module 'react-transition-group/utils/PropTypes' { 50 | declare module.exports: any; 51 | } 52 | 53 | declare module 'react-transition-group/utils/SimpleSet' { 54 | declare module.exports: any; 55 | } 56 | 57 | // Filename aliases 58 | declare module 'react-transition-group/CSSTransition.js' { 59 | declare module.exports: $Exports<'react-transition-group/CSSTransition'>; 60 | } 61 | declare module 'react-transition-group/dist/react-transition-group.js' { 62 | declare module.exports: $Exports<'react-transition-group/dist/react-transition-group'>; 63 | } 64 | declare module 'react-transition-group/dist/react-transition-group.min.js' { 65 | declare module.exports: $Exports<'react-transition-group/dist/react-transition-group.min'>; 66 | } 67 | declare module 'react-transition-group/index' { 68 | declare module.exports: $Exports<'react-transition-group'>; 69 | } 70 | declare module 'react-transition-group/index.js' { 71 | declare module.exports: $Exports<'react-transition-group'>; 72 | } 73 | declare module 'react-transition-group/Transition.js' { 74 | declare module.exports: $Exports<'react-transition-group/Transition'>; 75 | } 76 | declare module 'react-transition-group/TransitionGroup.js' { 77 | declare module.exports: $Exports<'react-transition-group/TransitionGroup'>; 78 | } 79 | declare module 'react-transition-group/utils/ChildMapping.js' { 80 | declare module.exports: $Exports<'react-transition-group/utils/ChildMapping'>; 81 | } 82 | declare module 'react-transition-group/utils/PropTypes.js' { 83 | declare module.exports: $Exports<'react-transition-group/utils/PropTypes'>; 84 | } 85 | declare module 'react-transition-group/utils/SimpleSet.js' { 86 | declare module.exports: $Exports<'react-transition-group/utils/SimpleSet'>; 87 | } 88 | -------------------------------------------------------------------------------- /flow-typed/npm/redux_v3.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: cca4916b0213065533df8335c3285a4a 2 | // flow-typed version: cab04034e7/redux_v3.x.x/flow_>=v0.55.x 3 | 4 | declare module 'redux' { 5 | 6 | /* 7 | 8 | S = State 9 | A = Action 10 | D = Dispatch 11 | 12 | */ 13 | 14 | declare export type DispatchAPI = (action: A) => A; 15 | declare export type Dispatch }> = DispatchAPI; 16 | 17 | declare export type MiddlewareAPI> = { 18 | dispatch: D; 19 | getState(): S; 20 | }; 21 | 22 | declare export type Store> = { 23 | // rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages) 24 | dispatch: D; 25 | getState(): S; 26 | subscribe(listener: () => void): () => void; 27 | replaceReducer(nextReducer: Reducer): void 28 | }; 29 | 30 | declare export type Reducer = (state: S | void, action: A) => S; 31 | 32 | declare export type CombinedReducer = (state: $Shape & {} | void, action: A) => S; 33 | 34 | declare export type Middleware> = 35 | (api: MiddlewareAPI) => 36 | (next: D) => D; 37 | 38 | declare export type StoreCreator> = { 39 | (reducer: Reducer, enhancer?: StoreEnhancer): Store; 40 | (reducer: Reducer, preloadedState: S, enhancer?: StoreEnhancer): Store; 41 | }; 42 | 43 | declare export type StoreEnhancer> = (next: StoreCreator) => StoreCreator; 44 | 45 | declare export function createStore(reducer: Reducer, enhancer?: StoreEnhancer): Store; 46 | declare export function createStore(reducer: Reducer, preloadedState?: S, enhancer?: StoreEnhancer): Store; 47 | 48 | declare export function applyMiddleware(...middlewares: Array>): StoreEnhancer; 49 | 50 | declare export type ActionCreator = (...args: Array) => A; 51 | declare export type ActionCreators = { [key: K]: ActionCreator }; 52 | 53 | declare export function bindActionCreators, D: DispatchAPI>(actionCreator: C, dispatch: D): C; 54 | declare export function bindActionCreators, D: DispatchAPI>(actionCreators: C, dispatch: D): C; 55 | 56 | declare export function combineReducers(reducers: O): CombinedReducer<$ObjMap(r: Reducer) => S>, A>; 57 | 58 | declare export var compose: $Compose; 59 | } 60 | -------------------------------------------------------------------------------- /flow-typed/npm/shortid_v2.2.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 3ac917fa8b1c70ef0f22392d6c66ddc6 2 | // flow-typed version: b43dff3e0e/shortid_v2.2.x/flow_>=v0.25.x 3 | 4 | type ShortIdModule = { 5 | (): string, 6 | generate(): string, 7 | seed(seed: number): ShortIdModule, 8 | worker(workerId: number): ShortIdModule, 9 | characters(characters: string): string, 10 | decode(id: string): { version: number, worker: number }, 11 | isValid(id: mixed): boolean, 12 | }; 13 | 14 | declare module 'shortid' { 15 | declare module.exports: ShortIdModule; 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cs-playground-react", 3 | "version": "0.4.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.17.1", 7 | "babel-standalone": "^6.26.0", 8 | "codemirror": "^5.31.0", 9 | "feather-icons": "^4.5.0", 10 | "flow-typed": "^2.4.0", 11 | "jshint": "^2.9.5", 12 | "lodash": "^4.17.4", 13 | "loop-protect": "^2.1.6", 14 | "react": "16.2.0", 15 | "react-codemirror2": "^3.0.7", 16 | "react-dom": "^16.0.0", 17 | "react-feather": "^1.0.8", 18 | "react-redux": "^5.0.6", 19 | "react-scripts": "1.1.0", 20 | "react-toastify": "^3.3.4", 21 | "react-tooltip": "^3.4.0", 22 | "react-transition-group": "^2.2.1", 23 | "redux": "^3.7.2", 24 | "shortid": "^2.2.8", 25 | "validator": "^9.4.1" 26 | }, 27 | "repository": "no-stack-dub-sack/cs-playground-react", 28 | "scripts": { 29 | "start": "react-scripts start", 30 | "build": "react-scripts build", 31 | "test": "react-scripts test --env=jsdom", 32 | "eject": "react-scripts eject", 33 | "postinstall": "flow-typed install", 34 | "pre-deploy": "yarn run build && cd build && cp index.html 200.html", 35 | "deploy": "yarn run pre-deploy && cd build && surge --domain cs-playground-react.surge.sh & cd ..", 36 | "deploy:test": "yarn run pre-deploy && cd build && surge --domain questionable-number.surge.sh && cd .." 37 | }, 38 | "devDependencies": { 39 | "flow-bin": "^0.66.0", 40 | "prop-types": "^15.6.0", 41 | "redux-devtools-extension": "^2.13.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no-stack-dub-sack/cs-playground-react/e978dd39d3f726d691bd55364a9b620e4deedae5/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Algorithms & Data Structures 11 | 12 | 13 |
14 | 15 |
16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/__tests__/AlgorithmChallenges.test.js: -------------------------------------------------------------------------------- 1 | import createJestTest from '../utils/test/app/create-jest-test' 2 | import { CODE } from '../assets/codeRef' 3 | import { forEach } from 'lodash' 4 | 5 | import { 6 | suppressConsole, 7 | enableConsole 8 | } from '../utils/test/app/jest-test-utils' 9 | 10 | const IDS = [ 11 | 'AnagramPalindrome', 12 | 'FlattenAnArray', 13 | 'GenerateCheckerboard', 14 | 'LongestCommonPrefix', 15 | 'NoTwoConsecutiveChars', 16 | 'SumAllPrimes', 17 | 'ReverseAString', 18 | 'ReverseVowels', 19 | ] 20 | 21 | describe('Algorithm Challenges: solution code passes tests', () => 22 | forEach(IDS, createJestTest)) 23 | 24 | /* eslint-disable no-eval */ 25 | describe('Algorithm Challenges: seed code does not have errors', () => { 26 | forEach(CODE.EASY_ALGOS, challenge => { 27 | test(`${challenge.title} seed compiles without errors`, () => { 28 | eval(suppressConsole + challenge.seed + enableConsole) 29 | expect(true).toBe(true) 30 | }) 31 | }) 32 | forEach(CODE.MODERATE_ALGOS, challenge => { 33 | test(`${challenge.title} seed compiles without errors`, () => { 34 | eval(suppressConsole + challenge.seed + enableConsole) 35 | expect(true).toBe(true) 36 | }) 37 | }) 38 | }) 39 | 40 | test('All Algorithm Challenges are being tested', () => 41 | expect(IDS.length).toEqual( 42 | CODE.EASY_ALGOS.length + 43 | CODE.MODERATE_ALGOS.length) 44 | ) 45 | -------------------------------------------------------------------------------- /src/__tests__/DataStructures.test.js: -------------------------------------------------------------------------------- 1 | import createJestTest from '../utils/test/app/create-jest-test' 2 | import { CODE } from '../assets/codeRef'; 3 | import { forEach } from 'lodash'; 4 | 5 | import { 6 | suppressConsole, 7 | enableConsole 8 | } from '../utils/test/app/jest-test-utils' 9 | 10 | const IDS = [ 11 | 'BinarySearchTree', 12 | 'CircularDoublyLinkedList', 13 | 'DoublyLinkedList', 14 | 'Graph', 15 | 'HashTable', 16 | 'LinkedList', 17 | 'MaxHeap', 18 | 'PriorityQueue', 19 | 'Queue', 20 | 'Stack', 21 | ] 22 | 23 | describe('Data Structures: solution code passes tests', () => 24 | forEach(IDS, createJestTest)) 25 | 26 | /* eslint-disable no-eval */ 27 | describe('Data Structure Challenges: seed code does not have errors', () => { 28 | forEach(CODE.DATA_STRUCTURES, challenge => { 29 | test(`${challenge.title} seed compiles without errors`, () => { 30 | eval(suppressConsole + challenge.seed + enableConsole) 31 | expect(true).toBe(true) 32 | }) 33 | }) 34 | }) 35 | 36 | test('All Data Structures are being tested', () => 37 | expect(IDS.length).toEqual(CODE.DATA_STRUCTURES.length)) 38 | -------------------------------------------------------------------------------- /src/__tests__/SortingAlgorithms.test.js: -------------------------------------------------------------------------------- 1 | import createJestTest from '../utils/test/app/create-jest-test' 2 | import { CODE } from '../assets/codeRef' 3 | import { forEach } from 'lodash'; 4 | 5 | import { 6 | suppressConsole, 7 | enableConsole 8 | } from '../utils/test/app/jest-test-utils' 9 | 10 | const IDS = [ 11 | 'BubbleSort', 12 | 'BucketSort', 13 | 'HeapSort', 14 | 'InsertionSort', 15 | 'Mergesort', 16 | 'Quicksort', 17 | 'SelectionSort', 18 | ] 19 | 20 | describe('Sorting Algorithms: solution code passes tests', () => 21 | forEach(IDS, createJestTest)) 22 | 23 | /* eslint-disable no-eval */ 24 | describe('Data Structure Challenges: seed code does not have errors', () => { 25 | forEach(CODE.SORTING_ALGOS, challenge => { 26 | // window.performance.now causes testing issues 27 | if (challenge.title !== 'Sorting Algorithm Benchmarks') { 28 | test(`${challenge.title} seed compiles without errors`, () => { 29 | eval(suppressConsole + challenge.seed + enableConsole) 30 | expect(true).toBe(true) 31 | }) 32 | } 33 | }) 34 | }) 35 | 36 | test('All Sorting Algos are being tested', () => 37 | expect(IDS.length).toEqual(CODE.SORTING_ALGOS.length-1)) 38 | -------------------------------------------------------------------------------- /src/actions/console.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { ClearConsole, ConsoleLog } from '../types/Actions'; 3 | 4 | export const clearConsole = (): ClearConsole => ({ type: 'CLEAR_CONSOLE' }) 5 | 6 | export const consoleLog = (logs: string): ConsoleLog => ({ 7 | type: 'CONSOLE_LOG', 8 | logs 9 | }) 10 | -------------------------------------------------------------------------------- /src/actions/editor.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { 4 | AddRepl, 5 | DeleteRepl, 6 | LoadRepl, 7 | NextChallenge, 8 | PrevChallenge, 9 | ResetState, 10 | SelectChallenge, 11 | SelectSolution, 12 | ToggleSolution, 13 | UpdateCode 14 | } from '../types/Actions'; 15 | 16 | export const loadRepl = (code: string): LoadRepl => ({ 17 | type: 'LOAD_SHARED_REPL', 18 | code 19 | }) 20 | 21 | export const addRepl = (id: string): AddRepl => ({ 22 | type: 'ADD_REPL', 23 | id 24 | }) 25 | 26 | export const nextChallenge = (): NextChallenge => ({ 27 | type: 'NEXT_CHALLENGE' 28 | }) 29 | 30 | export const prevChallenge = (): PrevChallenge => ({ 31 | type: 'PREV_CHALLENGE' 32 | }) 33 | 34 | export const selectChallenge = (id: string): SelectChallenge => ({ 35 | type: 'SELECT_CHALLENGE', 36 | id 37 | }) 38 | 39 | export const selectSolution = (id: string): SelectSolution => ({ 40 | type: 'SELECT_SOLUTION', 41 | id 42 | }) 43 | 44 | export const toggleSolution = (id: string): ToggleSolution => ({ 45 | type: 'TOGGLE_SOLUTION', 46 | id 47 | }) 48 | 49 | export const resetEditorState = (): ResetState => ({ 50 | type: 'RESET_STATE' 51 | }) 52 | 53 | export const updateCode = (code: string): UpdateCode => ({ 54 | type: 'UPDATE_CODE', 55 | code 56 | }) 57 | 58 | export const deleteRepl = (id: string): DeleteRepl => ({ 59 | type: 'DELETE_REPL', 60 | id 61 | }) 62 | -------------------------------------------------------------------------------- /src/actions/menu.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { filter, head } from 'lodash' 3 | 4 | import type { MenuState } from '../types/Reducers'; 5 | import type { ToggleMenu } from '../types/Actions'; 6 | 7 | export const toggleMenu = ( 8 | data: { name: string, open: boolean } 9 | ): ToggleMenu => ({ 10 | type: 'TOGGLE_MENU', 11 | data 12 | }) 13 | 14 | export const isMenuOpen = (menuState: MenuState, name: string): boolean => 15 | head(filter(menuState, menuItem => 16 | menuItem.name === name 17 | )).open 18 | -------------------------------------------------------------------------------- /src/actions/panes.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { DragHorizontal, DragVertical } from '../types/Actions'; 3 | 4 | export const doubleClick = () => ({ type: 'DOUBLE_CLICK' }) 5 | 6 | export const dragHorizontal = (cur: number, right: number): DragHorizontal => ({ 7 | type: 'DRAG_HORIZONTAL', 8 | leftWidth: cur + "%", 9 | rightWidth: right + "%" 10 | }) 11 | 12 | export const dragVertical = (cur: number, bottom: number): DragVertical => ({ 13 | type: 'DRAG_VERTICAL', 14 | topHeight: cur + "%", 15 | bottomHeight: bottom + "%" 16 | }) 17 | -------------------------------------------------------------------------------- /src/actions/theme.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { NextTheme, PrevTheme } from '../types/Actions'; 3 | 4 | export const nextTheme = (): NextTheme => ({ type: 'NEXT_THEME' }) 5 | export const prevTheme = (): PrevTheme => ({ type: 'PREV_THEME' }) 6 | -------------------------------------------------------------------------------- /src/assets/__ChallengeTemplate__.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: '', 3 | seed: 4 | `/** 5 | * @ 6 | */ 7 | 8 | `, 9 | solution: 10 | `/** 11 | * @ 12 | */ 13 | 14 | `, 15 | resources: [ 16 | { href: '', caption: ''}, 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/assets/__TestsTemplate__.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tail only needed for more complex challenges 3 | * use object prototypes to define hidden methods 4 | * wrap hidden method names in __ to avoid naming conflicts: ____ 5 | * use testHooks to define hooks to initialize or clear data structures 6 | * and to run any other funcs you may need to before, afteer or during execution 7 | */ 8 | export const tail = ` 9 | // for example: 10 | // .prototype.__clear__ = function() { 11 | // 12 | // } 13 | 14 | // delete any hook you aren't using 15 | // remove entire object if not using 16 | const testHooks = { 17 | beforeAll: () => { 18 | 19 | }, 20 | beforeEach: () => { 21 | 22 | }, 23 | afterEach: () => { 24 | 25 | }, 26 | afterAll: () => { 27 | 28 | } 29 | } 30 | ` 31 | export const tests = [ 32 | { 33 | expression: `typeof === 'function'`, 34 | message: ` is a function` 35 | }, 36 | { 37 | expression: `typeof === 'object'`, 38 | message: `The exists` 39 | }, 40 | // IIFE format, when the function resolves to a truthy value test will pass 41 | { 42 | expression: `(() => { 43 | // truthy values pass 44 | // falsy values fail 45 | })()`, 46 | message: `` 47 | }, 48 | // you can use this format if you need other methods available on assert 49 | // such as deepEqual, deepStrictEqual, notDeepEqual, notDeepStrictEqual, etc. 50 | { 51 | method: 'deepEqual', 52 | expression: ``, 53 | expected: ``, // value to compare to resolved value of expression 54 | message: `` 55 | }, 56 | // more tests here 57 | ]; 58 | -------------------------------------------------------------------------------- /src/assets/codeRef.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { forEach, replace } from 'lodash' 3 | 4 | import AnagramPalindrome from './seed/algorithms/AnagramPalindrome' 5 | import BinarySearchTree from './seed/data-structures/BinarySearchTree' 6 | import BubbleSort from './seed/algorithms/BubbleSort' 7 | import BucketSort from './seed/algorithms/BucketSort' 8 | import CircularDoublyLinkedList from './seed/data-structures/CircularDoublyLinkedList' 9 | import DoublyLinkedList from './seed/data-structures/DoublyLinkedList' 10 | import FlattenAnArray from './seed/algorithms/FlattenAnArray' 11 | import FreeCode from './seed/FreeCode' 12 | import GenerateCheckerboard from './seed/algorithms/GenerateCheckerboard' 13 | import Graph from './seed/data-structures/Graph' 14 | import HashTable from './seed/data-structures/HashTable' 15 | import HeapSort from './seed/algorithms/HeapSort' 16 | import InsertionSort from './seed/algorithms/InsertionSort' 17 | import LinkedList from './seed/data-structures/LinkedList' 18 | import LongestCommonPrefix from './seed/algorithms/LongestCommonPrefix' 19 | import MaxHeap from './seed/data-structures/MaxHeap' 20 | import Mergesort from './seed/algorithms/Mergesort' 21 | import NoTwoConsecutiveChars from './seed/algorithms/NoTwoConsecutiveChars' 22 | import PriorityQueue from './seed/data-structures/PriorityQueue' 23 | import Queue from './seed/data-structures/Queue' 24 | import Quicksort from './seed/algorithms/Quicksort' 25 | import ReverseAString from './seed/algorithms/ReverseAString' 26 | import ReverseVowels from './seed/algorithms/ReverseVowels' 27 | import SelectionSort from './seed/algorithms/SelectionSort' 28 | import SortingAlgorithmBenchmarks from './seed/algorithms/SortingBenchmarks' 29 | import Stack from './seed/data-structures/Stack' 30 | import SumAllPrimes from './seed/algorithms/SumAllPrimes' 31 | 32 | // NOTE: order of arrays determines order of sidebar menu 33 | 34 | export type Challenge = { 35 | title: string, 36 | seed: string, 37 | solution: string, 38 | resources: { 39 | href: string, 40 | caption: string 41 | }[] 42 | } 43 | 44 | export type Code = { 45 | SORTING_ALGOS: Challenge[], 46 | DATA_STRUCTURES: Challenge[], 47 | EASY_ALGOS: Challenge[], 48 | MODERATE_ALGOS: Challenge[], 49 | REPLS: Challenge[], 50 | } 51 | 52 | export const CODE: Code = { 53 | SORTING_ALGOS: [ 54 | Quicksort, 55 | Mergesort, 56 | SelectionSort, 57 | InsertionSort, 58 | BubbleSort, 59 | HeapSort, 60 | BucketSort, 61 | SortingAlgorithmBenchmarks, 62 | ], 63 | DATA_STRUCTURES: [ 64 | Stack, 65 | Queue, 66 | PriorityQueue, 67 | LinkedList, 68 | DoublyLinkedList, 69 | CircularDoublyLinkedList, 70 | BinarySearchTree, 71 | MaxHeap, 72 | HashTable, 73 | Graph 74 | ], 75 | EASY_ALGOS: [ 76 | SumAllPrimes, 77 | GenerateCheckerboard, 78 | FlattenAnArray, 79 | ReverseAString, 80 | ReverseVowels, 81 | // IsPalindrome, 82 | // FizzBuzz, 83 | ], 84 | MODERATE_ALGOS: [ 85 | LongestCommonPrefix, 86 | NoTwoConsecutiveChars, 87 | AnagramPalindrome, 88 | // RotateAnImage, 89 | ], 90 | REPLS: [ 91 | FreeCode 92 | ] 93 | } 94 | 95 | type SolutionsMap = Map 96 | 97 | const createSolutionsRef = (CODE: Code): SolutionsMap => { 98 | const map: SolutionsMap = new Map() 99 | for (let category: string in CODE) { 100 | let challenges: Challenge[] = CODE[category] 101 | forEach( 102 | challenges, 103 | challenge => { 104 | if (challenge.solution) { 105 | map.set( 106 | replace(challenge.title, /\s/g, ''), 107 | challenge.solution 108 | ) 109 | } 110 | }) 111 | } 112 | return map 113 | } 114 | 115 | export const SOLUTIONS = createSolutionsRef(CODE) 116 | -------------------------------------------------------------------------------- /src/assets/seed/FreeCode.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Free_Code', 3 | seed: `// No guidelines, no challenge, no tests! 4 | // Use this repl as a true playground to test things out and play around with different ideas. 5 | // Like any other challenge, your code here will be saved when you navigate away, provided you don't clear your browser history. 6 | // Add more repls below to add new ideas you'd like to save!`, 7 | solution: '', 8 | resources: [] 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/AnagramPalindrome.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Anagram Palindrome', 3 | seed: 4 | `// given a ramdom string of letters, return true if the letters 5 | // can be rearranged to form a palindrome, otherwise, return false 6 | 7 | // Ex1. 8 | // Input: pacescaps 9 | // Output: true 10 | 11 | // Ex2. 12 | // Input: javascript 13 | // Output: false 14 | 15 | /** 16 | * @function anagramPalindrome 17 | * @param {string} str 18 | * @returns {boolean} 19 | */ 20 | 21 | function anagramPalindrome(str) { 22 | return true 23 | } 24 | 25 | console.log(anagramPalindrome('armdabbmaboobrd')) // bombard a drab mob => true 26 | console.log(anagramPalindrome('armdabsbmaboobrd')) // bombards a drab mob => false 27 | console.log(anagramPalindrome('tdolgsaetagdliadaoasaasinvdeavn')) // a santa dog lived as a devil god at nasa => true 28 | console.log(anagramPalindrome('raoistddtagstonveakaaeawfewosln')) // a santa dog lived at nasa for two weeks => false 29 | `, 30 | solution: 31 | `// given a ramdom string of letters, return true if the letters 32 | // can be rearranged to form a palindrome, otherwise, return false 33 | 34 | // Ex1. 35 | // Input: pacescaps 36 | // Output: true 37 | 38 | // Ex2. 39 | // Input: javascript 40 | // Output: false 41 | 42 | /** 43 | * @function anagramPalindrome 44 | * @param {string} str 45 | * @returns {boolean} 46 | */ 47 | 48 | function anagramPalindrome(str) { 49 | var freq = {}, odds = 0 50 | 51 | for (let letter of str) { 52 | freq[letter] = -~freq[letter] 53 | } 54 | 55 | for (let letter in freq) { 56 | if (freq[letter] % 2 !== 0) { 57 | odds++ 58 | if (odds > 1) { 59 | return false 60 | } 61 | } 62 | } 63 | 64 | return true 65 | } 66 | 67 | console.log(anagramPalindrome('armdabbmaboobrd')) // bombard a drab mob 68 | console.log(anagramPalindrome('armdabsbmaboobrd')) // bombards a drab mob 69 | console.log(anagramPalindrome('tdolgsaetagdliadaoasaasinvdeavn')) // a santa dog lived as a devil god at nasa 70 | console.log(anagramPalindrome('raoistddtagstonveakaaeawfewosln')) // a santa dog lived at nasa for two weeks 71 | `, 72 | resources: [ 73 | { href: 'https://www.hackerrank.com/challenges/game-of-thrones/problem', caption: 'Hackerrank Challenge' }, 74 | { href: 'http://www.geeksforgeeks.org/check-given-string-rotation-palindrome/', caption: 'GeeksForGeeks.org' }, 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/BubbleSort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Bubble Sort', 3 | seed: 4 | `/** 5 | * @function bubbleSort 6 | * @param {number[]} arr 7 | * @returns {number[]} 8 | */ 9 | 10 | function bubbleSort(arr) { 11 | return arr 12 | } 13 | 14 | console.log(bubbleSort([23, 563, 0, 0, 2, 29, 8, 67, 22, 345, 11, 9, 53, 8])) 15 | `, 16 | solution: 17 | `/** 18 | * @function bubbleSort 19 | * @param {number[]} arr 20 | * @returns {number[]} 21 | */ 22 | 23 | function bubbleSort(arr) { 24 | var swapped = true 25 | 26 | while (swapped) { 27 | swapped = false 28 | for (let i = 0; i < arr.length; i++) { 29 | if (arr[i] > arr[i+1]) { 30 | [ arr[i], arr[i+1] ] = [ arr[i+1], arr[i] ] 31 | swapped = true 32 | } 33 | } 34 | } 35 | 36 | return arr 37 | } 38 | 39 | console.log(bubbleSort([23, 563, 0, 0, 2, 29, 8, 67, 22, 345, 11, 9, 53, 8])) 40 | `, 41 | resources: [ 42 | { href: 'http://www.geeksforgeeks.org/bubble-sort/', caption: 'GeeksforGeeks.org'}, 43 | { href: 'https://www.nczonline.net/blog/2009/05/26/computer-science-in-javascript-bubble-sort/', caption: 'NCZOnline Blog (JS Specific)'}, 44 | { href: 'https://beta.freecodecamp.org/en/challenges/coding-interview-algorithm-questions/implement-bubble-sort', caption: 'freeCodeCamp Challenge'}, 45 | { href: 'https://en.wikipedia.org/wiki/Bubble_sort', caption: 'Wikipedia'}, 46 | { href: 'https://guide.freecodecamp.org/algorithms/sorting-algorithms/bubble-sort/', caption: 'freeCodeCamp Guides'}, 47 | { href: 'https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html', caption: 'Awesome Sorting Algo Visualizations!'}, 48 | { href: 'https://visualgo.net/en/sorting', caption: 'VisualAlgo.net Sorting Algo Visualizations!'}, 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/BucketSort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Bucket Sort', 3 | seed: 4 | `/** 5 | * @function bucketSort 6 | * @param {number[]} arr 7 | * @returns {number[]} 8 | */ 9 | 10 | // there are many different implementations of this algorithm, this is just one! 11 | // Bucket sort is mainly useful when input is uniformly distributed over a range. 12 | // For exmaple, sorting a large set of floating point numbers which are in range 13 | // from 0.0 to 1.0 and are uniformly distributed across the range. see the link: 14 | // http://www.geeksforgeeks.org/bucket-sort-2/. Note that the solution provided 15 | // here would fail with a set of whole numbers which are not evenly distrubed. 16 | 17 | function bucketSort(arr) { 18 | return arr 19 | } 20 | 21 | console.log( 22 | bucketSort([ 23 | 0.77, 0.39, 0.26, 0.33, 0.55, 0.71, 24 | 0.23, 0.88, 0.47, 0.52, 0.72, 0.99, 25 | 0.63, 0.45, 0.21, 0.12, 0.23, 0.94 26 | ]) 27 | ) 28 | `, 29 | solution: 30 | `/** 31 | * @function bucketSort 32 | * @param {number[]} arr 33 | * @returns {number[]} 34 | */ 35 | 36 | // there are many different implementations of this algorithm, this is just one! 37 | // Bucket sort is mainly useful when input is uniformly distributed over a range. 38 | // For exmaple, sorting a large set of floating point numbers which are in range 39 | // from 0.0 to 1.0 and are uniformly distributed across the range. see the link: 40 | // http://www.geeksforgeeks.org/bucket-sort-2/. Note that the solution provided 41 | // here would fail with a set of whole numbers which are not evenly distrubed. 42 | 43 | function bucketSort(arr) { 44 | const len = arr.length 45 | let buckets = [...Array(len)].map(i => Array()) 46 | 47 | for (let i = 0; i < arr.length; i++) { 48 | let bucket = Math.floor(len*arr[i]) 49 | buckets[bucket].push(arr[i]) 50 | } 51 | 52 | for (let bucket of buckets) { 53 | if (bucket.length) { 54 | insertionSort(bucket) 55 | } 56 | } 57 | 58 | return buckets.reduce((result, el) => result.concat(el), []) 59 | } 60 | 61 | /** 62 | * @function insertionSort 63 | * @param {number[]} arr 64 | * @returns {number[]} 65 | */ 66 | 67 | // we use insertion sort to sort each bucket. 68 | // also note that this would become much less 69 | // efficent if buckets were too densely distrubed 70 | 71 | function insertionSort(arr) { 72 | for (let i = 0; i < arr.length; i++) { 73 | let j = i+1 74 | while (arr[j] < arr[j-1]) { 75 | [ arr[j], arr[j-1] ] = [ arr[j-1], arr[j] ] 76 | j-- 77 | } 78 | } 79 | 80 | return arr 81 | } 82 | 83 | console.log( 84 | bucketSort([ 85 | 0.77, 0.39, 0.26, 0.33, 0.55, 0.71, 86 | 0.23, 0.88, 0.47, 0.52, 0.72, 0.99, 87 | 0.63, 0.45, 0.21, 0.12, 0.23, 0.94 88 | ]) 89 | ) 90 | `, 91 | resources: [ 92 | { href: 'http://www.geeksforgeeks.org/bucket-sort-2/', caption: 'GeeksforGeeks.org'}, 93 | { href: 'https://en.wikipedia.org/wiki/Bucket_sort', caption: 'Wikipedia'}, 94 | { href: 'https://initjs.org/bucket-sort-in-javascript-dc040b8f0058', caption: 'Another Bucket Sort Implementation'}, 95 | { href: 'https://www.cs.usfca.edu/~galles/visualization/BucketSort.html', caption: 'Awesome Bucket Sort Visualizations!'}, 96 | ] 97 | } 98 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/FlattenAnArray.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Flatten An Array', 3 | seed: 4 | `/** 5 | * @function steamRoller 6 | * @param {*[]} arr An multi-dimensional array of arbitrary depth 7 | * @return {*[]} 8 | */ 9 | 10 | // There are so many ways to flatten an array! Flattening a 2d array is super easy, but 11 | // in this algorithm, make sure you can account for flattening arrays of any arbitrary 12 | // depth: 3d, 5d, 10d, 55d! Ok, let's not get carried away, but you get the point. Use 13 | // this array as a test case. Once flat, this array should contain all of it's original 14 | // values, in their original order, but all in a single one-dimensional array. Since this 15 | // this challenge is pretty easy, see how many different ways you can come up with to 16 | // solve this before looking at the solution, and see how short and effecient you can 17 | // make your own solution! One-liner anyone? :-) Good luck! 18 | 19 | const steamRoller = (arr) => { 20 | return arr; 21 | } 22 | 23 | // looks crazy, is valid: 24 | let superNestedArray = [ 25 | [ 'deep', [ 1, 2, [ 3, 'four', [ [ 'fifty-five', 2020, [ 100, [ [ [ 'whoah' ] ] ] ], 26 | [ 'ok' ] ] ], { 'really?': 'yes' }, [ [ [ 200, 42 ] ] ], 700, '700' ] ] ], [ [ [ 27 | [ 'deeper' ] ], 'no', null, undefined ] ], 28 | [ [ [ [ 'deepest-est?', [ [ [ 'nope!' ] ] ] ] ] ] 29 | ] 30 | ]; 31 | 32 | console.log(steamRoller(superNestedArray)); 33 | 34 | // should return: 35 | // [ 'deep', 1, 2, 3, 'four', 'fifty-five', 2020, 100, 'whoah', 'ok', { 'really?': 'yes' }, 200, 42, 700, '700', 'deeper', 'no', null, undefined, 'deepest-est?', 'nope!' ] 36 | `, 37 | solution: 38 | `/** 39 | * @function steamRoller 40 | * @param {*[]} arr An multi-dimensional array of arbitrary depth 41 | * @return {*[]} 42 | */ 43 | 44 | const steamRoller = (arr) => { 45 | const flat = [].concat(...arr); 46 | return flat.some(Array.isArray) ? steamRoller(flat) : flat 47 | } 48 | 49 | // looks crazy, is valid: 50 | let superNestedArray = [ 51 | [ 'deep', [ 1, 2, [ 3, 'four', [ [ 'fifty-five', 2020, [ 100, [ [ [ 'whoah' ] ] ] ], 52 | [ 'ok' ] ] ], { 'really?': 'yes' }, [ [ [ 200, 42 ] ] ], 700, '700' ] ] ], [ [ [ 53 | [ 'deeper' ] ], 'no', null, undefined ] ], 54 | [ [ [ [ 'deepest-est?', [ [ [ 'nope!' ] ] ] ] ] ] 55 | ] 56 | ]; 57 | 58 | console.log(steamRoller(superNestedArray)) 59 | `, 60 | resources: [ 61 | { href: 'https://www.codetuts.tech/flatten-deep-nested-array-object/', caption: 'Cool article on flattening techniques' }, 62 | { href: 'http://blog.benoitvallon.com/tips/flattening-arrays-in-javascript/', caption: 'Ben\'s Blog: Recursive and Iterative Approaches' } 63 | ] 64 | }; 65 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/GenerateCheckerboard.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Generate Checkerboard', 3 | seed: 4 | `/** @function generateCheckerboard 5 | * @param {number} h The height of the board 6 | * @param {number} w The width of the board 7 | * @returns {string} 8 | * 9 | * create an algorithm that takes two ints as 10 | * arguments, height and width, and returns a 11 | * string representing a checker board pattern 12 | * like so (8X8 board): 13 | * 14 | * # # # # # # # # 15 | * # # # # # # # # 16 | * # # # # # # # # 17 | * # # # # # # # # 18 | * # # # # # # # # 19 | * # # # # # # # # 20 | * # # # # # # # # 21 | * # # # # # # # # 22 | * 23 | * Good luck! 24 | * 25 | */ 26 | 27 | function generateCheckerboard(h, w) { 28 | return 29 | } 30 | 31 | // change the h & w for different size boards! 32 | const h = 8, w = 8 33 | 34 | console.log(generateCheckerboard(h, w)) 35 | `, 36 | solution: 37 | `/** @function generateCheckerBoard 38 | * @param {number} h The height of the board 39 | * @param {number} w The width of the board 40 | * @returns {string} 41 | */ 42 | 43 | function generateCheckerboard(h, w) { 44 | var row = '', board = '' 45 | for (let i = 0; i < w; i++) { 46 | row += "# " 47 | } 48 | 49 | row += '\\n' 50 | 51 | for (let i = 0; i < h; i++) { 52 | if (i % 2 === 0) { 53 | board += row 54 | } else { 55 | board += ' ' + row 56 | } 57 | } 58 | 59 | return board 60 | } 61 | 62 | // change the h & w for different size boards! 63 | const h = 8, w = 8 64 | 65 | console.log(generateCheckerboard(h, w)) 66 | `, 67 | resources: [ 68 | { href: 'https://en.wikipedia.org/wiki/Checkerboard#/media/File:Checkerboard_pattern.svg', caption: 'Here\'s what a checkerboard looks like!'}, 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/HeapSort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Heap Sort', 3 | seed: 4 | `class MinHeap { 5 | constructor() { 6 | this.heap = [] 7 | } 8 | 9 | // methods to implement: 10 | 11 | // insert(number) <= where the magic happens! 12 | // remove() <= where the magic happens! 13 | // print() 14 | // sort() 15 | // size() 16 | } 17 | 18 | var heap = new MinHeap() 19 | 20 | const unsorted = [72,3,19,24,99,45,33,0,2,43,17,19,22,80,100] 21 | // unsorted.forEach(num => heap.insert(num)) 22 | // heap.print() 23 | // const sorted = heap.sort() 24 | // console.log('\\nheap sort: ' + JSON.stringify(sorted)) 25 | `, 26 | solution: 27 | `/** 28 | * @class MinHeap 29 | * @property {number[]} heap A collection of integers 30 | * @method insert @param {numnber} node 31 | * @method remove @returns {?number} returns null or the removed item 32 | * @method print Logs the heap to the console 33 | * @method sort @returns {number[]} returns the sorted heap 34 | * @method size @returns {number} returns the size of the heap 35 | */ 36 | 37 | class MinHeap { 38 | constructor() { 39 | this.heap = [] 40 | } 41 | 42 | 43 | insert(node) { 44 | this.heap.push(node) 45 | 46 | var swap = (node, nodeIdx) => { 47 | 48 | var parentIdx = Math.floor((nodeIdx - 1) / 2) 49 | var parent = this.heap[parentIdx] 50 | 51 | if (parent > node) { 52 | this.heap[parentIdx] = node 53 | this.heap[nodeIdx] = parent 54 | swap(node, parentIdx) 55 | } 56 | } 57 | 58 | if (this.heap.length > 1) { 59 | return swap(node, this.heap.length-1) 60 | } 61 | } 62 | 63 | 64 | remove() { 65 | if (!this.size) { 66 | return null 67 | } 68 | 69 | var min = this.heap.shift() 70 | 71 | if (this.size > 1) { 72 | this.heap.unshift(this.heap.pop()) 73 | } 74 | 75 | var swap = (node, nodeIdx = 0) => { 76 | var childIdx 77 | if (this.size === 2) { 78 | childIdx = 1 79 | } else if (this.heap[2 * nodeIdx + 1] < this.heap[2 * nodeIdx + 2]) { 80 | childIdx = 2 * nodeIdx + 1 81 | } else { 82 | childIdx = 2 * nodeIdx + 2 83 | } 84 | 85 | if (node > this.heap[childIdx]) { 86 | this.heap[nodeIdx] = this.heap[childIdx] 87 | this.heap[childIdx] = node 88 | return swap(node, childIdx) 89 | } 90 | 91 | return min 92 | 93 | } 94 | 95 | return swap(this.heap[0]) 96 | } 97 | 98 | 99 | print() { 100 | console.log('min heap: ' + JSON.stringify(this.heap)) 101 | console.log('size: ' + this.size) 102 | } 103 | 104 | 105 | sort() { 106 | var sorted = [] 107 | 108 | while (this.size) { 109 | sorted.push(this.remove()) 110 | } 111 | 112 | return sorted 113 | } 114 | 115 | 116 | get size() { 117 | return this.heap.length 118 | } 119 | } 120 | 121 | var heap = new MinHeap() 122 | 123 | const unsorted = [72,3,19,24,99,45,33,0,2,43,17,19,22,80,100] 124 | unsorted.forEach(num => heap.insert(num)) 125 | 126 | heap.print() 127 | 128 | const sorted = heap.sort() 129 | console.log('\\nheap sort: ' + JSON.stringify(sorted)) 130 | `, 131 | resources: [ 132 | { href: 'http://www.geeksforgeeks.org/heap-sort/', caption: 'GeeksforGeeks.org'}, 133 | { href: 'https://learn.freecodecamp.org/coding-interview-prep/data-structures/implement-heap-sort-with-a-min-heap', caption: 'freeCodeCamp Challenge'}, 134 | { href: 'https://en.wikipedia.org/wiki/Heapsort', caption: 'Wikipedia'}, 135 | { href: 'https://guide.freecodecamp.org/algorithms/sorting-algorithms/heap-sort/', caption: 'freeCodeCamp Guides'}, 136 | { href: 'https://www.cs.usfca.edu/~galles/visualization/HeapSort.html', caption: 'Awesome Animated Sorting Algo Visualizations!'}, 137 | { href: 'https://www.hackerearth.com/practice/algorithms/sorting/heap-sort/tutorial/', caption: 'Another cool visualization'}, 138 | ] 139 | } 140 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/InsertionSort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Insertion Sort', 3 | seed: 4 | `/** 5 | * @function insertionSort 6 | * @param {number[]} arr 7 | * @returns {number[]} 8 | */ 9 | 10 | function insertionSort(arr) { 11 | return arr 12 | } 13 | 14 | console.log(insertionSort([56, 1, 2, 56, 767, 9, 9732, 0, 99, 11, 34, 87, 234, 1, 54])) 15 | `, 16 | solution: 17 | `/** 18 | * @function insertionSort 19 | * @param {number[]} arr 20 | * @returns {number[]} 21 | */ 22 | 23 | function insertionSort(arr) { 24 | for (var i = 0; i < arr.length; i++) { 25 | var j = i+1 26 | while (arr[j] < arr[j-1]) { 27 | [ arr[j], arr[j-1] ] = [ arr[j-1], arr[j] ] 28 | j-- 29 | } 30 | } 31 | 32 | return arr 33 | } 34 | 35 | console.log(insertionSort([56, 1, 2, 56, 767, 9, 9732, 0, 99, 11, 34, 87, 234, 1, 54])) 36 | `, 37 | resources: [ 38 | { href: 'http://www.geeksforgeeks.org/insertion-sort/', caption: 'GeeksforGeeks.org'}, 39 | { href: 'https://www.nczonline.net/blog/2012/09/17/computer-science-in-javascript-insertion-sort/', caption: 'NCZOnline Blog (JS Specific)'}, 40 | { href: 'https://beta.freecodecamp.org/en/challenges/coding-interview-algorithm-questions/implement-insertion-sort', caption: 'freeCodeCamp Challenge'}, 41 | { href: 'https://en.wikipedia.org/wiki/Insertion_sort', caption: 'Wikipedia'}, 42 | { href: 'https://guide.freecodecamp.org/algorithms/sorting-algorithms/insertion-sort/', caption: 'freeCodeCamp Guides'}, 43 | { href: 'https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html', caption: 'Awesome Sorting Algo Visualizations!'}, 44 | { href: 'https://visualgo.net/en/sorting', caption: 'VisualAlgo.net Sorting Algo Visualizations!'}, 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/LongestCommonPrefix.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Longest Common Prefix', 3 | seed: 4 | `/** 5 | * @function longestCommonPrefix 6 | * @param {string[]} strings 7 | * @return {string} 8 | */ 9 | 10 | // find and return the longest common prefix ammong an array 11 | // of strings. return false if no such common prefix exists. 12 | 13 | // ex1: ['people', 'peeps', 'person', 'pendulum'] => 'pe' 14 | // ex2: ['hello', 'hers', 'his', 'hint', 'hurts'] => 'h' 15 | // ex3: ['goodbye', 'good', 'ok', 'great', 'bad'] => false 16 | 17 | const longestCommonPrefix = (strings) => { 18 | return strings[0]; 19 | } 20 | 21 | console.log(longestCommonPrefix(['cooler', 'coolest', 'cool', 'cooling', 'cooled'])); 22 | `, 23 | solution: 24 | `/** 25 | * @function longestCommonPrefix 26 | * @param {string[]} strings 27 | * @return {string} 28 | */ 29 | 30 | const longestCommonPrefix = (strings) => { 31 | if (!strings.length) return false; 32 | 33 | let prefix = strings[0]; 34 | for (let i = 1; i < strings.length; i++) { 35 | prefix = findCommon(prefix, strings[i]); 36 | if (!prefix) return false; 37 | } 38 | 39 | function findCommon(str1, str2) { 40 | let i = 0, prefix = ''; 41 | while (i < str1.length && str1[i] === str2[i]) { 42 | prefix += str1[i++]; 43 | } 44 | return prefix; 45 | } 46 | 47 | return prefix; 48 | } 49 | 50 | console.log(longestCommonPrefix(['cooler', 'coolest', 'cool', 'cooling', 'cooled'])); 51 | `, 52 | resources: [ 53 | { href: 'https://leetcode.com/problems/longest-common-prefix/solution/', caption: 'LeetCode: Dicussion of various solutions' } 54 | ] 55 | }; 56 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/Mergesort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Mergesort', 3 | seed: 4 | `/** 5 | * @function mergeSort 6 | * @param {number[]} arr 7 | * @returns {number[]} 8 | */ 9 | 10 | function mergeSort(arr) { 11 | return arr 12 | } 13 | 14 | console.log(mergeSort([27698, 234, 98, 0, 23, 11, 9, 65, 3, 4, 0, 2, 1])) 15 | `, 16 | solution: 17 | `/** 18 | * @function mergeSort 19 | * @param {number[]} arr 20 | * @returns {number[]} 21 | */ 22 | 23 | function mergeSort(arr) { 24 | if (arr.length < 2) { 25 | return arr 26 | } 27 | 28 | var left = arr.slice(0, arr.length/2) 29 | var right = arr.slice(arr.length/2) 30 | 31 | return merge(mergeSort(left), mergeSort(right)) 32 | } 33 | 34 | /** 35 | * @function merge 36 | * @param {number[]} left 37 | * @param {number[]} right 38 | * @returns {number[]} 39 | */ 40 | 41 | function merge(left, right) { 42 | var results = [], 43 | idxL = 0, 44 | idxR = 0 45 | 46 | while (idxL < left.length && idxR < right.length) { 47 | if (left[idxL] <= right[idxR]) { 48 | results.push(left[idxL++]) 49 | } else { 50 | results.push(right[idxR++]) 51 | } 52 | } 53 | 54 | return results.concat(left.slice(idxL), right.slice(idxR)) 55 | } 56 | 57 | console.log(mergeSort([27698, 234, 98, 0, 23, 11, 9, 65, 3, 4, 0, 2, 1])) 58 | `, 59 | resources: [ 60 | { href: 'http://www.geeksforgeeks.org/merge-sort/', caption: 'GeeksforGeeks.org'}, 61 | { href: 'https://www.nczonline.net/blog/2012/10/02/computer-science-and-javascript-merge-sort/', caption: 'NCZOnline Blog (JS Specific)'}, 62 | { href: 'https://beta.freecodecamp.org/en/challenges/coding-interview-algorithm-questions/implement-merge-sort', caption: 'freeCodeCamp Challenge'}, 63 | { href: 'https://en.wikipedia.org/wiki/Merge_sort', caption: 'Wikipedia'}, 64 | { href: 'https://guide.freecodecamp.org/algorithms/sorting-algorithms/merge-sort/', caption: 'freeCodeCamp Guides'}, 65 | { href: 'https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html', caption: 'Awesome Sorting Algo Visualizations!'}, 66 | { href: 'https://visualgo.net/en/sorting', caption: 'VisualAlgo.net Sorting Algo Visualizations!'}, 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/NoTwoConsecutiveChars.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'No Two Consecutive Chars', 3 | seed: 4 | `// Given a random string, return a new string containing all the 5 | // characters of the original string, but no 2 characters should 6 | // be consecutive. If such a string can't be created, return false. 7 | 8 | // Ex1. 9 | // Input: ABBCCD 10 | // Output: ABCBCD 11 | 12 | // Ex2. 13 | // Input: AAAB 14 | // Output: false 15 | 16 | /** 17 | * @function noTwoConsecutiveChars 18 | * @param {string} str The String to operate on 19 | * @returns {(string|bool)} Rearranged string or false 20 | */ 21 | 22 | function noTwoConsecutiveChars(str) { 23 | 24 | return str 25 | } 26 | 27 | console.log(noTwoConsecutiveChars('aaba')) 28 | console.log(noTwoConsecutiveChars('aabba')) 29 | console.log(noTwoConsecutiveChars('aaaaaaabbbbcc')) 30 | console.log(noTwoConsecutiveChars('aaabaaabbbbbbbbbccccbbcbsd')) 31 | console.log(noTwoConsecutiveChars('aaabaaabbbbbbbbbbccccbbcbsd')) 32 | console.log(noTwoConsecutiveChars('aaabaaabbbbbbbbbbbccccbbcbsd')) 33 | `, 34 | solution: 35 | `// Given a random string, return a new string containing all the 36 | // characters of the original string, but no 2 characters should 37 | // be consecutive. If such a string can't be created, return false. 38 | 39 | // Ex1. 40 | // Input: ABBCCD 41 | // Output: ABCBCD 42 | 43 | // Ex2. 44 | // Input: AAAB 45 | // Output: false 46 | 47 | /** 48 | * @function noTwoConsecutiveChars 49 | * @param {string} str The String to operate on 50 | * @returns {(string|bool)} Rearranged string or false 51 | */ 52 | 53 | function noTwoConsecutiveChars(str) { 54 | 55 | var freqTable = {} 56 | 57 | for (var char of str) { 58 | freqTable[char] = -~freqTable[char] 59 | } 60 | 61 | 62 | function getNextChar(newStr = '', lastChar = '') { 63 | var mostFreq = 0, 64 | nextMostFreq = 0, 65 | nextChar = '' 66 | 67 | for (var char in freqTable) { 68 | if (freqTable[char] > mostFreq) { 69 | mostFreq = freqTable[char] 70 | nextChar = char 71 | } 72 | 73 | if (nextChar === lastChar) { 74 | if (freqTable[char] > nextMostFreq && char !== lastChar) { 75 | nextMostFreq = freqTable[char] 76 | nextChar = char 77 | } 78 | } 79 | } 80 | 81 | 82 | if (!mostFreq) { 83 | return newStr 84 | } else if (nextChar === lastChar) { 85 | return false 86 | } else { 87 | newStr+=nextChar 88 | lastChar = nextChar 89 | freqTable[lastChar]-- 90 | return getNextChar(newStr, lastChar) 91 | } 92 | } 93 | 94 | return getNextChar() 95 | } 96 | 97 | console.log(noTwoConsecutiveChars('aaba')) 98 | console.log(noTwoConsecutiveChars('aabba')) 99 | console.log(noTwoConsecutiveChars('aaaaaaabbbbcc')) 100 | console.log(noTwoConsecutiveChars('aaabaaabbbbbbbbbccccbbcbsd')) 101 | console.log(noTwoConsecutiveChars('aaabaaabbbbbbbbbbccccbbcbsd')) 102 | console.log(noTwoConsecutiveChars('aaabaaabbbbbbbbbbbccccbbcbsd')) 103 | `, 104 | resources: [ 105 | { href: 'http://www.geeksforgeeks.org/rearrange-characters-string-no-two-adjacent/', caption: 'GeeksforGeeks.org'}, 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/Quicksort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Quicksort', 3 | seed: 4 | `/** 5 | * @function quickSort 6 | * @param {number[]} arr 7 | * @returns {number[]} 8 | */ 9 | 10 | function quickSort(arr) { 11 | return arr 12 | } 13 | 14 | console.log(quickSort([6, 9, 23, 3564, 0, 4, 99, 11, 25, 74, 939, 35, 1, 643, 3, 75])) 15 | `, 16 | solution: 17 | `/** 18 | * @function quickSort 19 | * @param {number[]} arr 20 | * @param {number} [low=0] 21 | * @param {number} [high=arr.length] 22 | * @returns {number[]} 23 | */ 24 | 25 | function quickSort(arr, low = 0, high = arr.length-1) { 26 | 27 | if (arr.length > 1) { 28 | 29 | var index = partition(arr, low, high) 30 | 31 | if (low < index - 1) { 32 | quickSort(arr, low, index-1) 33 | } 34 | 35 | if (high > index) { 36 | quickSort(arr, index, high) 37 | } 38 | } 39 | 40 | return arr 41 | } 42 | 43 | /** 44 | * @function partition 45 | * @param {number[]} arr 46 | * @param {number} [low=0] 47 | * @param {number} [high=arr.length] 48 | * @returns {number[]} 49 | */ 50 | 51 | function partition(arr, low, high) { 52 | var pivot = arr[Math.floor((low+high)/2)], 53 | i = low, 54 | j = high 55 | 56 | while (i <= j) { 57 | 58 | while (arr[i] < pivot) { 59 | i++ 60 | } 61 | 62 | while (arr[j] > pivot) { 63 | j-- 64 | } 65 | 66 | if (i <= j) { 67 | [ arr[i], arr[j] ] = [ arr[j], arr[i] ] 68 | j-- 69 | i++ 70 | } 71 | 72 | } 73 | 74 | return i 75 | } 76 | 77 | console.log(quickSort([6, 9, 23, 3564, 0, 4, 99, 11, 25, 74, 939, 35, 1, 643, 3, 75])) 78 | `, 79 | resources: [ 80 | { href: 'http://www.geeksforgeeks.org/quick-sort/', caption: 'GeeksforGeeks.org'}, 81 | { href: 'https://www.nczonline.net/blog/2012/11/27/computer-science-in-javascript-quicksort/', caption: 'NCZOnline Blog (JS Specific)'}, 82 | { href: 'https://beta.freecodecamp.org/en/challenges/coding-interview-algorithm-questions/implement-quick-sort', caption: 'freeCodeCamp Challenge'}, 83 | { href: 'https://en.wikipedia.org/wiki/Quicksort', caption: 'Wikipedia'}, 84 | { href: 'https://guide.freecodecamp.org/algorithms/sorting-algorithms/quick-sort', caption: 'freeCodeCamp Guides'}, 85 | { href: 'https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html', caption: 'Awesome Sorting Algo Visualizations!'}, 86 | { href: 'https://visualgo.net/en/sorting', caption: 'VisualAlgo.net Sorting Algo Visualizations!'}, 87 | { href: 'https://www.youtube.com/watch?v=MZaf_9IZCrc', caption: 'Youtube Quicksort Visualization'}, 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/ReverseAString.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Reverse A String', 3 | seed: 4 | `/** 5 | * @function reverseAString 6 | * @param {string} str - The string to reverse 7 | * @returns {string} 8 | */ 9 | 10 | // All you have to do is return the characters in the string 11 | // in reverse order. Punctuation, spaces, and capitalization 12 | // should be treated in the same as any other character. 13 | // The most efficient approach here will depend on the length 14 | // of your string. But try to think about scalability, and see 15 | // if you can avoid using the built-in .reverse function! 16 | 17 | function reverseAString(myString) { 18 | // Do stuff here 19 | return myString; 20 | } 21 | 22 | console.log(reverseAString('gnirtSym')) 23 | `, 24 | solution: 25 | `// challenge submitted by @garroadran 26 | 27 | /** 28 | * @function reverseAString 29 | * @param {string} str - The string to reverse 30 | * @returns {string} 31 | */ 32 | 33 | // While not the most concise approach, this solution 34 | // has the benefit of working quickly on very large 35 | // strings and a small memory overhead. 36 | 37 | function reverseAString(str) { 38 | const arr = str.split(''); 39 | 40 | // Define pointers 41 | let left = 0; 42 | let right = arr.length - 1; 43 | 44 | while(left < right) { 45 | // Swap the characters at the pointer positions 46 | const x = arr[left]; 47 | arr[left] = arr[right]; 48 | arr[right] = x; 49 | 50 | // Move the pointers toward each other 51 | left += 1; 52 | right -= 1; 53 | } 54 | 55 | return arr.join(''); 56 | } 57 | 58 | console.log(reverseAString('!skrow ti ,looc')) 59 | `, 60 | resources: [ 61 | { href: 'http://eddmann.com/posts/ten-ways-to-reverse-a-string-in-javascript/', caption: 'Article by Edd Mann'}, 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/ReverseVowels.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Reverse Vowels', 3 | seed: 4 | `/** 5 | * @function reverseVowels 6 | * @param {string} string - The string to process 7 | * @returns {string} - String with vowels reversed 8 | */ 9 | 10 | // Given a random string, reverse its vowels and return the string, that's it! 11 | // e.g., input: 'hello', output: 'holle'. Hint: RegExp may come in handy here 12 | // Note: we are not counting letter 'Y' as a vowel, and you should ignore case 13 | 14 | const reverseVowels = (string) => { 15 | 16 | return string 17 | } 18 | 19 | console.log(reverseVowels('ence the vawils ire reversed thas well moke senso!')) 20 | `, 21 | solution: 22 | `/** 23 | * @function reverseVowels 24 | * @param {string} string - The string to process 25 | * @returns {string} - String with vowels reversed 26 | */ 27 | 28 | const reverseVowels = (string) => { 29 | // get array of all vowels in string 30 | const vowels = string.match(/[aeiou]/gi) 31 | 32 | // split and map, replacing each vowel 33 | // with vowel popped off of vowels array 34 | // rejoin and return mutated string 35 | return string 36 | .split('') 37 | .map(letter => 38 | /[aeiou]/i.test(letter) 39 | ? vowels.pop() 40 | : letter 41 | ).join('') 42 | } 43 | 44 | console.log(reverseVowels('ence the vawils ire reversed thas well moke senso!')) 45 | `, 46 | resources: [ 47 | { href: 'https://gist.github.com/primaryobjects/5af99a12fc047f9d10c26b2faf6a374b', caption: 'A few different approaches'}, 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/SelectionSort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Selection Sort', 3 | seed: 4 | `/** 5 | * @function selectionSort 6 | * @param {number[]} arr 7 | * @returns {number[]} 8 | */ 9 | 10 | function selectionSort(arr) { 11 | return arr 12 | } 13 | 14 | console.log(selectionSort([5, 23, 9876, 21, 0, 11, 2, 67, 89, 234, 0, 12, 43, 694])) 15 | `, 16 | solution: 17 | `/** 18 | * @function selectionSort 19 | * @param {number[]} arr 20 | * @returns {number[]} 21 | */ 22 | 23 | function selectionSort(arr) { 24 | var pointer = 0 25 | while (pointer < arr.length) { 26 | var min = arr[pointer], swapIdx 27 | for (var i = pointer; i < arr.length; i++) { 28 | if (arr[i] <= min) { 29 | min = arr[i] 30 | swapIdx = i 31 | } 32 | } 33 | 34 | var temp = arr[pointer] 35 | arr[pointer] = min 36 | arr[swapIdx] = temp 37 | pointer++ 38 | } 39 | 40 | return arr 41 | } 42 | 43 | // NOTE: If your solution looks a little different than this, you can see a couple of other correct but 44 | // slightly modified implementations of selection sort here: https://repl.it/@no_stack_dub_sack/Selection-Sort. 45 | 46 | console.log(selectionSort([5, 23, 9876, 21, 0, 11, 2, 67, 89, 234, 0, 12, 43, 694])) 47 | `, 48 | resources: [ 49 | { href: 'http://www.geeksforgeeks.org/selection-sort/', caption: 'GeeksforGeeks.org'}, 50 | { href: 'https://www.nczonline.net/blog/2009/09/08/computer-science-in-javascript-selection-sort/', caption: 'NCZOnline Blog (JS Specific)'}, 51 | { href: 'https://beta.freecodecamp.org/en/challenges/coding-interview-algorithm-questions/implement-selection-sort', caption: 'freeCodeCamp Challenge'}, 52 | { href: 'https://en.wikipedia.org/wiki/Selection_sort', caption: 'Wikipedia'}, 53 | { href: 'https://guide.freecodecamp.org/algorithms/sorting-algorithms/selection-sort/', caption: 'freeCodeCamp Guides'}, 54 | { href: 'https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html', caption: 'Awesome Sorting Algo Visualizations!'}, 55 | { href: 'https://visualgo.net/en/sorting', caption: 'VisualAlgo.net Sorting Algo Visualizations!'}, 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /src/assets/seed/algorithms/SumAllPrimes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Sum All Primes', 3 | seed: 4 | `// given a number, return the sum of all prime 5 | // numbers less than or equal to the number itself 6 | 7 | /** 8 | * @function sumAllPrimes 9 | * @param {number} num 10 | * @returns {number} 11 | */ 12 | 13 | function sumAllPrimes(num) { 14 | return num 15 | } 16 | 17 | console.log('sumAllPrimes(977) => ' + sumAllPrimes(977)) 18 | `, 19 | solution: 20 | `// given a number, return the sum of all prime 21 | // numbers less than or equal to the number itself 22 | 23 | /** 24 | * @function sumAllPrimes 25 | * @param {number} num 26 | * @returns {number} 27 | */ 28 | 29 | function sumAllPrimes(num) { 30 | let arr = Array.from({ length: num + 1 }, (v, k) => k).slice(2) 31 | let onlyPrimes = arr.filter(n => { 32 | let m = n - 1 33 | while (m > 1 && m >= Math.sqrt(n)) { 34 | if (n % m === 0) return false 35 | m-- 36 | } 37 | return true 38 | }) 39 | 40 | if (!onlyPrimes.length) { 41 | return 0 42 | } 43 | 44 | return onlyPrimes.reduce((a, b) => a + b) 45 | } 46 | 47 | console.log('sumAllPrimes(977) => ' + sumAllPrimes(977)) 48 | `, 49 | resources: [ 50 | { href: 'https://www.freecodecamp.org/challenges/sum-all-primes', caption: 'freeCodeCamp Challenge' }, 51 | { href: 'https://guide.freecodecamp.org/certificates/sum-all-primes', caption: 'freeCodeCamp Guides (solutions)' }, 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /src/assets/seed/data-structures/MaxHeap.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Max Heap', 3 | seed: 4 | `class MaxHeap { 5 | constructor() { 6 | this.heap = [] 7 | } 8 | 9 | // methods to implement 10 | 11 | // insert(number) 12 | // remove() 13 | // sort() 14 | // print() 15 | // size() 16 | } 17 | `, 18 | solution: 19 | `/** 20 | * @class MaxHeap 21 | * @property {number[]} heap The heap's collection 22 | * @method insert {number} node Inserts number according to max heap principle 23 | * @method remove @returns {number} Returns the max value of the heap 24 | * @method sort @returns {number[]} returns the sorted heap 25 | * @method print Prints the heap to the console 26 | * @method size @returns {number} Returns the size of the heap 27 | */ 28 | 29 | class MaxHeap { 30 | constructor() { 31 | this.heap = [] 32 | } 33 | 34 | 35 | insert(number) { 36 | this.heap.push(number) 37 | 38 | const swap = (nodeIdx) => { 39 | 40 | const parentIdx = Math.floor((nodeIdx - 1) / 2) 41 | const parent = this.heap[parentIdx] 42 | 43 | if (parent < number) { 44 | this.heap[parentIdx] = number 45 | this.heap[nodeIdx] = parent 46 | swap(parentIdx) 47 | } 48 | } 49 | 50 | if (this.heap.length > 1) { 51 | return swap(this.heap.length-1) 52 | } 53 | } 54 | 55 | 56 | remove() { 57 | if (!this.size) { 58 | return null 59 | } 60 | 61 | const max = this.heap.shift() 62 | 63 | if (this.size > 1) { 64 | this.heap.unshift(this.heap.pop()) 65 | } 66 | 67 | const swap = (node, nodeIdx = 0) => { 68 | let childIdx 69 | if (this.size === 2) { 70 | childIdx = 1 71 | } else if (this.heap[2 * nodeIdx + 1] > this.heap[2 * nodeIdx + 2]) { 72 | childIdx = 2 * nodeIdx + 1 73 | } else { 74 | childIdx = 2 * nodeIdx + 2 75 | } 76 | 77 | if (node < this.heap[childIdx]) { 78 | this.heap[nodeIdx] = this.heap[childIdx] 79 | this.heap[childIdx] = node 80 | return swap(node, childIdx) 81 | } 82 | 83 | return max 84 | } 85 | 86 | return swap(this.heap[0]) 87 | } 88 | 89 | 90 | sort() { 91 | var sorted = [] 92 | while (this.size) { 93 | sorted.push(this.remove()) 94 | } 95 | return sorted.reverse() 96 | } 97 | 98 | 99 | print() { 100 | console.log(this.heap) 101 | } 102 | 103 | 104 | get size() { 105 | return this.heap.length 106 | } 107 | } 108 | 109 | const heap = new MaxHeap() 110 | 111 | const nums = [7, 10, 14, 32, 2, 64, 37] 112 | 113 | for (let num of nums) { 114 | heap.insert(num) 115 | } 116 | 117 | heap.print() 118 | console.log(\`\\nremove \${heap.remove()}\\n\`) 119 | heap.print() 120 | console.log(\`\\nremove \${heap.remove()}\\n\`) 121 | heap.print() 122 | `, 123 | resources: [ 124 | { href: 'http://www.geeksforgeeks.org/heap-data-structure/', caption: 'GeeksforGeeks.org'}, 125 | { href: 'https://learn.freecodecamp.org/coding-interview-prep/data-structures/insert-an-element-into-a-max-heap', caption: 'freeCodeCamp Challenge'}, 126 | { href: 'https://en.wikipedia.org/wiki/Heap_(data_structure)', caption: 'Wikipedia'}, 127 | { href: 'https://www.cs.usfca.edu/~galles/visualization/Heap.html', caption: 'Interactive Animated Visualization!'}, 128 | { href: 'https://visualgo.net/en/heap', caption: 'VisualAlgo.net: Better Interactive Animated Visualization!'}, 129 | ] 130 | } 131 | -------------------------------------------------------------------------------- /src/assets/seed/data-structures/Stack.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Stack', 3 | seed: 4 | `class Node { 5 | constructor(value) { 6 | this.value = value 7 | this.next = null 8 | } 9 | } 10 | 11 | class Stack { 12 | constructor() { 13 | this.root = null 14 | this.size = 0 15 | } 16 | 17 | // methods to implement: 18 | 19 | // push(value) 20 | // pop() 21 | // peek() 22 | // isEmpty() 23 | // clear() 24 | // print() 25 | } 26 | `, 27 | solution: 28 | `// improved solution subimtted by @kingstenbanh 29 | 30 | /** 31 | * @class Node 32 | * @property {(number|string)} value The node's value 33 | * @property {?Object.} next The next node 34 | */ 35 | 36 | class Node { 37 | constructor(value) { 38 | this.value = value 39 | this.next = null 40 | } 41 | } 42 | 43 | /** 44 | * @class Stack data structure 45 | * @property {?Object.} root The root of the collection 46 | * @property {number} size The size of the collection 47 | * @method push @param {(number|string)} value Adds an element to the collection 48 | * @method pop @returns {(number|string)} Removes the top node from the stack and returns its value 49 | * @method peek @returns {(number|string)} Returns the value of the node at the top of the Stack 50 | * @method isEmpty @returns {boolean} 51 | * @method clear Clears the stack 52 | * @method print Prints the collection to the console 53 | */ 54 | 55 | class Stack { 56 | constructor() { 57 | this.root = null 58 | this.size = 0 59 | } 60 | 61 | 62 | push(value) { 63 | const node = new Node(value) 64 | 65 | if (this.root === null) { 66 | this.root = node 67 | } else { 68 | node.next = this.root 69 | this.root = node 70 | } 71 | 72 | this.size++ 73 | } 74 | 75 | 76 | pop() { 77 | if (this.isEmpty()) return null 78 | 79 | const value = this.root.value 80 | this.root = this.root.next 81 | this.size-- 82 | 83 | return value 84 | } 85 | 86 | 87 | peek() { 88 | return this.root 89 | ? this.root.value 90 | : null 91 | } 92 | 93 | 94 | isEmpty() { 95 | return this.size === 0 96 | } 97 | 98 | 99 | clear() { 100 | this.root = null 101 | this.size = 0 102 | } 103 | 104 | print() { 105 | console.log(JSON.stringify(this, null, 2)) 106 | } 107 | } 108 | 109 | // example usage: 110 | 111 | const stack = new Stack() 112 | 113 | console.log('isEmpty: ' + stack.isEmpty()) 114 | 115 | stack.push(23) 116 | stack.push(47) 117 | stack.push(95) 118 | 119 | console.log('pop: ' + stack.pop()) 120 | console.log('pop: ' + stack.pop()) 121 | console.log('pop: ' + stack.pop()) 122 | console.log('pop: ' + stack.pop()); 123 | 124 | [49,27,63,18,11] 125 | .forEach(num => stack.push(num)) 126 | 127 | console.log('peek: ' + stack.peek()) 128 | console.log('size: ' + stack.size) 129 | console.log('isEmpty: ' + stack.isEmpty() + '\\n') 130 | 131 | stack.print() 132 | stack.clear() 133 | console.log('\\ncleared:\\n\\n') 134 | stack.print() 135 | `, 136 | resources: [ 137 | { href: 'http://www.geeksforgeeks.org/stack-data-structure/', caption: 'GeeksforGeeks.org'}, 138 | { href: 'http://www.geeksforgeeks.org/implementation-stack-javascript/', caption: 'GeeksforGeeks.org JS Implementation'}, 139 | { href: 'https://learn.freecodecamp.org/coding-interview-prep/data-structures/create-a-stack-class', caption: 'freeCodeCamp Challenge Series'}, 140 | { href: 'https://en.wikipedia.org/wiki/Stack_(abstract_data_type)', caption: 'Wikipedia'}, 141 | { href: 'https://guide.freecodecamp.org/computer-science/data-structures/stacks', caption: 'freeCodeCamp Guides'}, 142 | { href: 'https://www.cs.usfca.edu/~galles/visualization/StackLL.html', caption: 'Interactive Animated Visualization!'}, 143 | { href: 'https://visualgo.net/en/list', caption: 'VisualAlgo.net: Better Interactive Animated Visualization!'}, 144 | ] 145 | } 146 | -------------------------------------------------------------------------------- /src/assets/testRef.js: -------------------------------------------------------------------------------- 1 | import * as BubbleSort from './tests/algorithms/BubbleSort' 2 | import * as BucketSort from './tests/algorithms/BucketSort' 3 | import * as HeapSort from './tests/algorithms/HeapSort' 4 | import * as InsertionSort from './tests/algorithms/InsertionSort' 5 | import * as Mergesort from './tests/algorithms/Mergesort' 6 | import * as Quicksort from './tests/algorithms/Quicksort' 7 | import * as SelectionSort from './tests/algorithms/SelectionSort' 8 | 9 | import * as BinarySearchTree from './tests/data-structures/BinarySearchTree' 10 | import * as Graph from './tests/data-structures/Graph' 11 | import * as HashTable from './tests/data-structures/HashTable' 12 | import * as LinkedList from './tests/data-structures/LinkedList' 13 | import * as DoublyLinkedList from './tests/data-structures/DoublyLinkedList' 14 | import * as CircularDoublyLinkedList from './tests/data-structures/CircularDoublyLinkedList' 15 | import * as MaxHeap from './tests/data-structures/MaxHeap' 16 | import * as PriorityQueue from './tests/data-structures/PriorityQueue' 17 | import * as Stack from './tests/data-structures/Stack' 18 | import * as Queue from './tests/data-structures/Queue' 19 | 20 | import * as AnagramPalindrome from './tests/algorithms/AnagramPalindrome' 21 | import * as FlattenAnArray from './tests/algorithms/FlattenAnArray' 22 | import * as GenerateCheckerboard from './tests/algorithms/GenerateCheckerboard' 23 | import * as LongestCommonPrefix from './tests/algorithms/LongestCommonPrefix' 24 | import * as NoTwoConsecutiveChars from './tests/algorithms/NoTwoConsecutiveChars' 25 | import * as SumAllPrimes from './tests/algorithms/SumAllPrimes' 26 | import * as ReverseAString from './tests/algorithms/ReverseAString' 27 | import * as ReverseVowels from './tests/algorithms/ReverseVowels' 28 | 29 | export default { 30 | // Sorting Algorithms 31 | BubbleSort, 32 | BucketSort, 33 | HeapSort, 34 | InsertionSort, 35 | Mergesort, 36 | Quicksort, 37 | SelectionSort, 38 | // Data Structures 39 | BinarySearchTree, 40 | Graph, 41 | HashTable, 42 | LinkedList, 43 | DoublyLinkedList, 44 | CircularDoublyLinkedList, 45 | MaxHeap, 46 | PriorityQueue, 47 | Stack, 48 | Queue, 49 | // Algorithm Challenges 50 | AnagramPalindrome, 51 | SumAllPrimes, 52 | FlattenAnArray, 53 | NoTwoConsecutiveChars, 54 | LongestCommonPrefix, 55 | GenerateCheckerboard, 56 | ReverseAString, 57 | ReverseVowels, 58 | } 59 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/AnagramPalindrome.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof anagramPalindrome === 'function'`, 4 | message: 'anagramPalindrome is a function' 5 | }, 6 | { 7 | expression: `typeof anagramPalindrome('string') === 'boolean'`, 8 | message: 'anagramPalindrome accepts a string as an argument and returns a boolean' 9 | }, 10 | { 11 | expression: `anagramPalindrome('armdabbmaboobrd') === true && anagramPalindrome('caerrac') === true`, 12 | message: 'anagramPalindrome returns true when the given string can be rearranged to form a palindrome' 13 | }, 14 | { 15 | expression: `anagramPalindrome('armdabsbmaboobrd') === false && anagramPalindrome('caerracs') === false`, 16 | message: 'anagramPalindrome returns false when the given string cannot be rearranged to form a palindrome' 17 | } 18 | ]; 19 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/BubbleSort.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof bubbleSort === 'function'`, 4 | message: 'bubbleSort is a function' 5 | }, 6 | { 7 | expression: `Array.isArray(bubbleSort([1, 2, 3])) && JSON.stringify(bubbleSort([1,2,3])) === '[1,2,3]'`, 8 | message: 'bubbleSort accepts and returns an array' 9 | }, 10 | { 11 | expression: `!/\\.sort\\s*\\(.*\\)/.test(bubbleSort.toString())`, 12 | message: 'bubbleSort does not use the built in Array.sort() method' 13 | }, 14 | { 15 | expression: ` 16 | (() => { 17 | return JSON.stringify(bubbleSort([6,9,23,3564,0,4,99,11,25,74,939,35,1,643,3,75])) === '[0,1,3,4,6,9,11,23,25,35,74,75,99,643,939,3564]' && 18 | JSON.stringify(bubbleSort([987654,54,86753,0,-9,233,111,0,12,9,12,33,4])) === '[-9,0,0,4,9,12,12,33,54,111,233,86753,987654]' && 19 | JSON.stringify(bubbleSort([5,9,10,1,0,2,5,3,2])) === '[0,1,2,2,3,5,5,9,10]'; 20 | })()`, 21 | message: 'bubbleSort sorts arrays from least to greatest' 22 | } 23 | ]; 24 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/BucketSort.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof bucketSort === 'function'`, 4 | message: 'bucketSort is a function' 5 | }, 6 | { 7 | expression: `Array.isArray(bucketSort([0.01,0.02,0.02])) && JSON.stringify(bucketSort([0.01,0.02,0.02])) === '[0.01,0.02,0.02]'`, 8 | message: 'bucketSort accepts and returns an array' 9 | }, 10 | { 11 | expression: `!/\\.sort\\s*\\(.*\\)/.test(bucketSort.toString())`, 12 | message: 'bucketSort does not use the built in Array.sort() method' 13 | }, 14 | { 15 | expression: ` 16 | (() => { 17 | return JSON.stringify(bucketSort([ 18 | 0.77, 0.39, 0.26, 0.33, 0.55, 0.71, 19 | 0.23, 0.88, 0.47, 0.52, 0.72, 0.99, 20 | 0.63, 0.45, 0.21, 0.12, 0.23, 0.94 21 | ])) === '[0.12,0.21,0.23,0.23,0.26,0.33,0.39,0.45,0.47,0.52,0.55,0.63,0.71,0.72,0.77,0.88,0.94,0.99]' && 22 | JSON.stringify(bucketSort([ 23 | 0.22, 0.01, 0.02, 0.0001, 0.0102, 0.0210, 24 | 0.011, 0.0233, 0.076, 0.088, 0.99, 0.0654 25 | ])) === '[0.0001,0.01,0.0102,0.011,0.02,0.021,0.0233,0.0654,0.076,0.088,0.22,0.99]'; 26 | })()`, 27 | message: 'bucketSort sorts arrays of floating point numbers from least to greatest' 28 | } 29 | ]; 30 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/FlattenAnArray.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof steamRoller === 'function'`, 4 | message: 'steamRoller is a function' 5 | }, 6 | { 7 | expression: `typeof Array.isArray(steamRoller([1, 2, 3]))`, 8 | message: 'steamRoller returns an array' 9 | }, 10 | { 11 | expression: ` 12 | (() => { 13 | const __nested__ = [1, [2, 3, 4, 5], 2, 3, [4, 5, 6]] 14 | const result = '[1,2,3,4,5,2,3,4,5,6]' 15 | return JSON.stringify(steamRoller(__nested__)) === result 16 | })()`, 17 | message: 'The steamRoller function takes a two-dimensional array as an argument and returns a one-dimensional array containing all of the original arrays\' elements' 18 | }, 19 | { 20 | expression: ` 21 | (() => { 22 | const __nested__ = [1, [2, 3, ['1', '2', '3'], true, false], null, 75, [24, 54, 62]] 23 | const result = '[1,2,3,"1","2","3",true,false,null,75,24,54,62]' 24 | return JSON.stringify(steamRoller(__nested__)) === result 25 | })()`, 26 | message: 'The steamRoller function takes a three-dimensional array as an argument and returns a one-dimensional array containing all of the original arrays\' elements' 27 | }, 28 | { 29 | expression: ` 30 | (() => { 31 | const __nested__ = [ 32 | [ 'deep', [ 1, 2, [ 3, 'four', [ [ 'fifty-five', 2020, [ 100, [ [ [ 'whoah' ] ] ] ], 33 | [ 'ok' ] ] ], { 'really?': 'yes' }, [ [ [ 200, 42 ] ] ], 700, '700' ] ] ], [ [ [ 34 | [ 'deeper' ] ], 'no', null, undefined ] ], 35 | [ [ [ [ 'deepest-est?', [ [ [ 'nope!' ] ] ] ] ] ] 36 | ] 37 | ] 38 | const result = '["deep",1,2,3,"four","fifty-five",2020,100,"whoah","ok",{"really?":"yes"},200,42,700,"700","deeper","no",null,null,"deepest-est?","nope!"]' 39 | return JSON.stringify(steamRoller(__nested__)) === result 40 | })()`, 41 | message: 'The steamRoller function takes a nested array of arbitrary depth and returns a one-dimensional array containing all of the original arrays\' elements' 42 | } 43 | ]; 44 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/GenerateCheckerboard.js: -------------------------------------------------------------------------------- 1 | export const tail = ` 2 | const board_1 = generateCheckerboard(8, 8); 3 | const board_2 = generateCheckerboard(16, 16); 4 | `; 5 | 6 | export const tests = [ 7 | { 8 | expression: "typeof generateCheckerboard === 'function'", 9 | message: 'generateCheckerboard is a function' 10 | }, 11 | { 12 | expression: "typeof board_1 === 'string'", 13 | message: 'generateCheckerboard returns a string' 14 | }, 15 | { 16 | expression: "typeof board_1 === 'string' && board_1.match(/#/g).length === 64", 17 | message: 'an 8x8 board has 64 # chars' 18 | }, 19 | { 20 | expression: "typeof board_1 === 'string' && board_1.match(/\\n/g).length === 8", 21 | message: 'an 8x8 board has 8 \\n chars' 22 | }, 23 | { 24 | expression: "typeof board_1 === 'string' && board_1.match(/ /g).length === 64 || board_1.match(/ /g).length === 68", 25 | message: 'an 8x8 board has 64 or 68 spaces' 26 | }, 27 | { 28 | expression: "typeof board_1 === 'string' && board_2.match(/#/g).length === 256", 29 | message: 'a 16x16 board has 256 # chars' 30 | }, 31 | { 32 | expression: "typeof board_1 === 'string' && board_2.match(/\\n/g).length === 16", 33 | message: 'a 16x16 board has 8 \\n chars' 34 | }, 35 | { 36 | expression: "typeof board_1 === 'string' && board_2.match(/ /g).length === 256 || board_2.match(/ /g).length === 264", 37 | message: 'a 16x16 board has between 256 or 264 spaces' 38 | }, 39 | { 40 | expression: ` 41 | (() => { 42 | let isPassing = true; 43 | [board_1, board_2].forEach(board => { 44 | board.split('\\n').forEach((row, i, arr) => { 45 | if (row) { 46 | if (i % 2 === 0) { 47 | isPassing = row[0] === '#'; 48 | } else { 49 | isPassing = row[0] === ' '; 50 | } 51 | } 52 | }); 53 | }); 54 | return isPassing; 55 | })() 56 | `, 57 | message: 'each even row begins with # and each odd row begins with a space' 58 | } 59 | ]; 60 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/HeapSort.js: -------------------------------------------------------------------------------- 1 | export const tail = ` 2 | if (typeof new MinHeap() === 'object') { 3 | MinHeap.prototype.__clear__ = function() { 4 | this.heap = [] 5 | return true 6 | } 7 | } 8 | 9 | let __heap__ 10 | const testHooks = { 11 | beforeAll: () => { 12 | __heap__ = new MinHeap() 13 | }, 14 | beforeEach: () => { 15 | __heap__.__clear__() 16 | typeof __heap__.insert === 'function' && 17 | [72,3,19,24,99,45,33,0].forEach(n => __heap__.insert(n)) 18 | }, 19 | afterAll: () => { 20 | __heap__ = null 21 | } 22 | } 23 | ` 24 | 25 | export const tests = [ 26 | { 27 | expression: `typeof __heap__ === 'object'`, 28 | message: 'The MinHeap data structure exists' 29 | }, 30 | { 31 | expression: ` 32 | (() => { 33 | __heap__.__clear__() 34 | return __heap__.heap && Array.isArray(__heap__.heap) && __heap__.heap.length === 0; 35 | })()`, 36 | message: 'The MinHeap data structure has a heap property, initialized as an empty array' 37 | }, 38 | { 39 | expression: `typeof __heap__.insert == 'function'`, 40 | message: 'MinHeap has a method called insert: @param {number} number' 41 | }, 42 | { 43 | expression: `JSON.stringify(__heap__.heap) === '[0,3,19,24,99,45,33,72]'`, 44 | message: 'The insert method adds elements according to the min heap property' 45 | }, 46 | { 47 | expression: `typeof __heap__.remove == 'function'`, 48 | message: 'MinHeap has a method called remove' 49 | }, 50 | { 51 | expression: ` 52 | (() => { 53 | if (__heap__.remove() !== 0) return false 54 | if (JSON.stringify(__heap__.heap) !== '[3,24,19,72,99,45,33]') 55 | return false 56 | if (__heap__.remove() !== 3) return false 57 | if (JSON.stringify(__heap__.heap) !== '[19,24,33,72,99,45]') 58 | return false 59 | if (__heap__.remove() !== 19) return false 60 | if (JSON.stringify(__heap__.heap) !== '[24,45,33,72,99]') 61 | return false 62 | if (__heap__.remove() !== 24) return false 63 | if (JSON.stringify(__heap__.heap) !== '[33,45,99,72]') 64 | return false 65 | return true; 66 | })() 67 | `, 68 | message: 'The remove method removes and returns elements according to the min heap property' 69 | }, 70 | { 71 | expression: `__heap__.__clear__() && __heap__.remove() === null`, 72 | message: 'The remove method returns null when called on an empty heap' 73 | }, 74 | { 75 | expression: `typeof __heap__.sort == 'function'`, 76 | message: 'MinHeap has a method called sort' 77 | }, 78 | { 79 | expression: `JSON.stringify(__heap__.sort()) === '[0,3,19,24,33,45,72,99]'`, 80 | message: 'The sort method returns a sorted array (from least to greatest) containing all the elements in the heap' 81 | } 82 | ]; 83 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/InsertionSort.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof insertionSort === 'function'`, 4 | message: 'insertionSort is a function' 5 | }, 6 | { 7 | expression: `Array.isArray(insertionSort([1, 2, 3])) && JSON.stringify(insertionSort([1,2,3])) === '[1,2,3]'`, 8 | message: 'insertionSort accepts and returns an array' 9 | }, 10 | { 11 | expression: `!/\\.sort\\s*\\(.*\\)/.test(insertionSort.toString())`, 12 | message: 'insertionSort does not use the built in Array.sort() method' 13 | }, 14 | { 15 | expression: ` 16 | (() => { 17 | return JSON.stringify(insertionSort([6,9,23,3564,0,4,99,11,25,74,939,35,1,643,3,75])) === '[0,1,3,4,6,9,11,23,25,35,74,75,99,643,939,3564]' && 18 | JSON.stringify(insertionSort([987654,54,86753,0,-9,233,111,0,12,9,12,33,4])) === '[-9,0,0,4,9,12,12,33,54,111,233,86753,987654]' && 19 | JSON.stringify(insertionSort([5,9,10,1,0,2,5,3,2])) === '[0,1,2,2,3,5,5,9,10]'; 20 | })()`, 21 | message: 'insertionSort sorts arrays from least to greatest' 22 | } 23 | ]; 24 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/LongestCommonPrefix.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof longestCommonPrefix === 'function'`, 4 | message: `longestCommonPrefix is a function` 5 | }, 6 | { 7 | expression: ` 8 | (() => { 9 | const __arr1__ = ['aaa', 'aaabbb', 'aaaaccc', 'aabbddd'] // aa 10 | const __arr2__ = ['cool', 'cooler', 'coolest', 'cooling'] // cool 11 | const __arr3__ = ['boolean', 'boost', 'booster', 'boon'] // boo 12 | const __arr4__ = ['boolean', 'boost', 'booster', 'boring'] // bo 13 | const __arr5__ = ['people', 'peeps', 'person', 'pendulum'] // 'pe' 14 | const __arr6__ = ['hello', 'hers', 'his', 'hint', 'hurts'] // 'h' 15 | const __arr7__ = ['a', 'ab', 'ac', 'ad'] // a 16 | if (longestCommonPrefix(__arr2__) !== 'cool') return false 17 | if (longestCommonPrefix(__arr3__) !== 'boo') return false 18 | if (longestCommonPrefix(__arr1__) !== 'aa') return false 19 | if (longestCommonPrefix(__arr4__) !== 'bo') return false 20 | if (longestCommonPrefix(__arr5__) !== 'pe') return false 21 | if (longestCommonPrefix(__arr6__) !== 'h') return false 22 | if (longestCommonPrefix(__arr7__) !== 'a') return false 23 | return true 24 | })()`, 25 | message: `longestCommonPrefix takes an array of strings and, whenever possible, returns a string representing the longest common prefix ammong the strings` 26 | }, 27 | { 28 | expression: ` 29 | (() => { 30 | const __arr1__ = ['aaa', 'aaabbb', 'aaaaccc', 'c'] 31 | const __arr2__ = ['cool', 'cooler', 'coolest', 'd'] 32 | const __arr3__ = ['boolean', 'boost', 'booster', 'q'] 33 | if (longestCommonPrefix(__arr2__) !== false) return false 34 | if (longestCommonPrefix(__arr3__) !== false) return false 35 | if (longestCommonPrefix(__arr1__) !== false) return false 36 | return true 37 | })()`, 38 | message: `When passed an array of strings that share no common prefix, longestCommonPrefix returns false` 39 | }, 40 | ] 41 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/Mergesort.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof mergeSort === 'function'`, 4 | message: 'mergeSort is a function' 5 | }, 6 | { 7 | expression: `Array.isArray(mergeSort([1, 2, 3])) && JSON.stringify(mergeSort([1,2,3])) === '[1,2,3]'`, 8 | message: 'mergeSort accepts and returns an array' 9 | }, 10 | { 11 | expression: `!/\\.sort\\s*\\(.*\\)/.test(mergeSort.toString())`, 12 | message: 'mergeSort does not use the built in Array.sort() method' 13 | }, 14 | { 15 | expression: ` 16 | (() => { 17 | return JSON.stringify(mergeSort([6,9,23,3564,0,4,99,11,25,74,939,35,1,643,3,75])) === '[0,1,3,4,6,9,11,23,25,35,74,75,99,643,939,3564]' && 18 | JSON.stringify(mergeSort([987654,54,86753,0,-9,233,111,0,12,9,12,33,4])) === '[-9,0,0,4,9,12,12,33,54,111,233,86753,987654]' && 19 | JSON.stringify(mergeSort([5,9,10,1,0,2,5,3,2])) === '[0,1,2,2,3,5,5,9,10]'; 20 | })()`, 21 | message: 'mergeSort sorts arrays from least to greatest' 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/NoTwoConsecutiveChars.js: -------------------------------------------------------------------------------- 1 | export const tail = ` 2 | const isValid = (str) => { 3 | for (let i = 1; i < str.length; i++) { 4 | if (str[i-1] === str[i]) { 5 | return false; 6 | } 7 | } 8 | 9 | return true; 10 | } 11 | 12 | const countChars = (str) => { 13 | const charsMap = {}; 14 | for (let char of str) { 15 | charsMap[char] = -~charsMap[char]; 16 | } 17 | 18 | return charsMap; 19 | } 20 | 21 | const compareChars = (map1, map2) => { 22 | const keys1 = Object.keys(map1).sort(); 23 | const keys2 = Object.keys(map2).sort(); 24 | if (JSON.stringify(keys1) !== JSON.stringify(keys2)) { 25 | return false; 26 | } 27 | 28 | for (let key in map1) { 29 | if (map1[key] !== map2[key]) { 30 | return false; 31 | } 32 | } 33 | 34 | return true; 35 | } 36 | `; 37 | export const tests = [ 38 | { 39 | expression: `typeof noTwoConsecutiveChars === 'function'`, 40 | message: 'noTwoConsecutiveChars is a function' 41 | }, 42 | { 43 | expression: ` 44 | (() => { 45 | const TEST_1 = isValid(noTwoConsecutiveChars('aabba')); 46 | const TEST_2 = isValid(noTwoConsecutiveChars('aaaaaaabbbbcc')); 47 | const TEST_3 = isValid(noTwoConsecutiveChars('aaabaaabbbbbbbbbccccbbcbsd')); 48 | const TEST_4 = isValid(noTwoConsecutiveChars('aaabaaabbbbbbbbbbccccbbcbsd')); 49 | const originalCharsMap_1 = countChars('aaabaaabbbbbbbbbbccccbbcbsd'); 50 | const resultCharsMap_1 = countChars(noTwoConsecutiveChars('aaabaaabbbbbbbbbbccccbbcbsd')); 51 | const originalCharsMap_2 = countChars('aaabaaabbbbbbbbbccccbbcbsd'); 52 | const resultCharsMap_2 = countChars(noTwoConsecutiveChars('aaabaaabbbbbbbbbccccbbcbsd')); 53 | const isSameChars_1 = compareChars(originalCharsMap_1, resultCharsMap_1); 54 | const isSameChars_2 = compareChars(originalCharsMap_2, resultCharsMap_2); 55 | return TEST_1 && TEST_2 && TEST_3 && TEST_4 && isSameChars_1 && isSameChars_2; 56 | })()`, 57 | message: 'Whenever possible, noTwoConsecutiveChars returns a string that contains all the characters from the original string, rearranged so that no two consecutive characters are the same' 58 | }, 59 | { 60 | expression: `noTwoConsecutiveChars('aaba') === false && noTwoConsecutiveChars('aaabaaabbbbbbbbbbbccccbbcbsd') === false && typeof noTwoConsecutiveChars('aaabaaabbbbbbbbbbccccbbcbsd') === 'string'`, 61 | message: 'noTwoConsecutiveChars returns false when a string with no consecutive chars can\'t be constructed' 62 | }, 63 | ]; 64 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/Quicksort.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof quickSort === 'function'`, 4 | message: 'quickSort is a function' 5 | }, 6 | { 7 | expression: `Array.isArray(quickSort([1, 2, 3])) && JSON.stringify(quickSort([1,2,3])) === '[1,2,3]'`, 8 | message: 'quickSort accepts and returns an array' 9 | }, 10 | { 11 | expression: `!/\\.sort\\s*\\(.*\\)/.test(quickSort.toString())`, 12 | message: 'quickSort does not use the built in Array.sort() method' 13 | }, 14 | { 15 | expression: ` 16 | (() => { 17 | return JSON.stringify(quickSort([6,9,23,3564,0,4,99,11,25,74,939,35,1,643,3,75])) === '[0,1,3,4,6,9,11,23,25,35,74,75,99,643,939,3564]' && 18 | JSON.stringify(quickSort([987654,54,86753,0,-9,233,111,0,12,9,12,33,4])) === '[-9,0,0,4,9,12,12,33,54,111,233,86753,987654]' && 19 | JSON.stringify(quickSort([5,9,10,1,0,2,5,3,2])) === '[0,1,2,2,3,5,5,9,10]'; 20 | })()`, 21 | message: 'quickSort sorts arrays from least to greatest' 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/ReverseAString.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof reverseAString === 'function'`, 4 | message: 'reverseAString is a function' 5 | }, 6 | { 7 | expression: `reverseAString('I like dogs!') === '!sgod ekil I'`, 8 | message: 'reverseAString("I like dogs!") returns "!sgod ekil I"' 9 | }, 10 | { 11 | expression: `reverseAString('Reverse me') === 'em esreveR'`, 12 | message: 'reverseAString("Reverse me") returns "em esreveR"' 13 | }, 14 | { 15 | expression: `(() => { 16 | if (reverseAString('cool') !== 'looc') 17 | return false 18 | if (reverseAString('won desrever mi') !== 'im reversed now') 19 | return false 20 | if (reverseAString('lsfjlskd sdlfhlsk sldkfslkf') !== 'fklsfkdls kslhflds dksljfsl') 21 | return false 22 | if (reverseAString('32 fsdl^%@hs 208fwhn08#$%3b32') !== '23b3%$#80nhwf802 sh@%^ldsf 23') 23 | return false 24 | return true 25 | })()`, 26 | message: `reverseAString correctly reverses any other string` 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/ReverseVowels.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof reverseVowels === 'function'`, 4 | message: `reverseVowels is a function` 5 | }, 6 | { 7 | expression: `reverseVowels('leorn to cade') === 'learn to code'`, 8 | message: `reverseVowels('leorn to cade') returns 'learn to code'` 9 | }, 10 | { 11 | expression: `reverseVowels('egnErA CiPaTel LOttIrS') === 'IgnOre CaPiTAl LEtterS'`, 12 | message: `reverseVowels('egnErA CiPaTel LOttIrS') returns a string with reversed vowels, ignoring case` 13 | }, 14 | { 15 | expression: `reverseVowels('Hey, __ignore__ non-word# chars!') === 'Hay, __ognore__ non-wird# chers!'`, 16 | message: `reverseVowels('Hey, __ignore__ non-word# chars!') returns a string with reversed vowels, ignoring non-word chars` 17 | }, 18 | { 19 | expression: `reverseVowels('xycghkl') === 'xycghkl'`, 20 | message: `reverseVowels returns the original string, unmutated, when it contains no vowels` 21 | }, 22 | { 23 | expression: `(() => { 24 | if (reverseVowels('sOoiuwEcoIhwueibdsadUh') !== 'sUaiewucIohwEuibdsodOh') 25 | return false 26 | if (reverseVowels('weErsE oihOn aeAUidESkjdi') !== 'wiErsi UAhen aOioEdESkjde') 27 | return false 28 | if (reverseVowels('cuil, et werks! leak ot mo rovirsong stoff') !== 'cool, it works! look at me reversing stuff') 29 | return false 30 | if (reverseVowels('#4obU!&seetUa^%!') !== '#4abU!&seetUo^%!') 31 | return false 32 | return true 33 | })()`, 34 | message: `reverseVowels correctly handles any other string according to these rules` 35 | }, 36 | ]; 37 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/SelectionSort.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof selectionSort === 'function'`, 4 | message: 'selectionSort is a function' 5 | }, 6 | { 7 | expression: `Array.isArray(selectionSort([1, 2, 3])) && JSON.stringify(selectionSort([1,2,3])) === '[1,2,3]'`, 8 | message: 'selectionSort accepts and returns an array' 9 | }, 10 | { 11 | expression: `!/\\.sort\\s*\\(.*\\)/.test(selectionSort.toString())`, 12 | message: 'selectionSort does not use the built in Array.sort() method' 13 | }, 14 | { 15 | expression: ` 16 | (() => { 17 | return JSON.stringify(selectionSort([6,9,23,3564,0,4,99,11,25,74,939,35,1,643,3,75])) === '[0,1,3,4,6,9,11,23,25,35,74,75,99,643,939,3564]' && 18 | JSON.stringify(selectionSort([987654,54,86753,0,-9,233,111,0,12,9,12,33,4])) === '[-9,0,0,4,9,12,12,33,54,111,233,86753,987654]' && 19 | JSON.stringify(selectionSort([5,9,10,1,0,2,5,3,2])) === '[0,1,2,2,3,5,5,9,10]'; 20 | })()`, 21 | message: 'selectionSort sorts arrays from least to greatest' 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /src/assets/tests/algorithms/SumAllPrimes.js: -------------------------------------------------------------------------------- 1 | export const tests = [ 2 | { 3 | expression: `typeof sumAllPrimes === 'function'`, 4 | message: 'sumAllPrimes is a function' 5 | }, 6 | { 7 | expression: `typeof sumAllPrimes(5) === 'number'`, 8 | message: 'sumAllPrimes returns a number' 9 | }, 10 | { 11 | expression: `sumAllPrimes(1) === 0 && sumAllPrimes(-11) === 0`, 12 | message: 'sumAllPrimes returns 0 if there are no prime numbers less than the argument provided' 13 | }, 14 | { 15 | expression: `sumAllPrimes(977) === 73156`, 16 | message: `sumAllPrimes(977) returns 73156` 17 | }, 18 | { 19 | expression: `sumAllPrimes(2000) === 277050`, 20 | message: `sumAllPrimes(2000) returns 277050` 21 | }, 22 | { 23 | expression: `sumAllPrimes(3450) === 761455`, 24 | message: `sumAllPrimes(3450) returns 761455` 25 | } 26 | ]; 27 | -------------------------------------------------------------------------------- /src/assets/tests/data-structures/HashTable.js: -------------------------------------------------------------------------------- 1 | /* NOTE: 2 | * If any tests are added or removed in this file, the tests which have changing messages 3 | * will need to ammended to be sure that the test at the correct index is being modified. 4 | * See tests[11] and test[13], for example. If another test is added before these tests 5 | * each index reference will have to be incremented. This is not ideal, and is part of 6 | * the reason why this cool way of using more dynamic test messages is not more widely 7 | * used throughout the codebase. I could not get a `this` reference to work correctly 8 | * so that this could be more flexible and not require these kinds of changes, e.g. 9 | * this.message = 'This doesn't work! Like, at all' `this` is always undefined 10 | */ 11 | 12 | export const tail = ` 13 | if (typeof new HashTable() === 'object') { 14 | HashTable.prototype.__clearTable__ = function() { 15 | this.data = {} 16 | return true 17 | } 18 | HashTable.prototype.__print__ = function() { 19 | return JSON.stringify(this.data) 20 | } 21 | } 22 | 23 | let __table__ 24 | const testHooks = { 25 | beforeAll: () => { 26 | __table__ = new HashTable() 27 | }, 28 | beforeEach: () => { 29 | __table__.__clearTable__() 30 | }, 31 | afterAll: () => { 32 | __table__ = null 33 | } 34 | } 35 | 36 | const isProperlyHashed = (tests, index) => { 37 | if (!__table__.data[1363]) { 38 | tests[index].message = 'There is no value stored at the expected hash key. Be sure to hash your key when you add using hash method and store your key/value pair at the key it returns.' 39 | return false 40 | } 41 | return true 42 | } 43 | ` 44 | 45 | export const tests = [ 46 | { 47 | expression: `typeof __table__ === 'object'`, 48 | message: `The HashTable data structure exists (no tests for this challenge!)` 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /src/assets/tests/data-structures/MaxHeap.js: -------------------------------------------------------------------------------- 1 | export const tail = ` 2 | if (typeof new MaxHeap() === 'object') { 3 | MaxHeap.prototype.__clear__ = function() { 4 | this.heap = [] 5 | return true 6 | } 7 | } 8 | 9 | let __heap__ 10 | const testHooks = { 11 | beforeAll: () => { 12 | __heap__ = new MaxHeap() 13 | }, 14 | beforeEach: () => { 15 | __heap__.__clear__() 16 | typeof __heap__.insert === 'function' && 17 | [7,10,14,32,2,64,37].forEach(n => __heap__.insert(n)) 18 | }, 19 | afterAll: () => { 20 | __heap__ = null 21 | } 22 | } 23 | ` 24 | 25 | export const tests = [ 26 | { 27 | expression: `typeof __heap__ === 'object'`, 28 | message: 'The MaxHeap data structure exists' 29 | }, 30 | { 31 | expression: ` 32 | (() => { 33 | __heap__.__clear__() 34 | return __heap__.heap && Array.isArray(__heap__.heap) && __heap__.heap.length === 0 35 | })()`, 36 | message: 'The MaxHeap data structure has a heap property, initialized as an empty Array object' 37 | }, 38 | { 39 | expression: `typeof __heap__.insert == 'function'`, 40 | message: 'MaxHeap has a method called insert: @param {number} number' 41 | }, 42 | { 43 | expression: `JSON.stringify(__heap__.heap) === '[64,14,37,7,2,10,32]'`, 44 | message: 'The insert method adds elements according to the max heap property' 45 | }, 46 | { 47 | expression: `typeof __heap__.remove == 'function'`, 48 | message: 'MaxHeap has a method called remove' 49 | }, 50 | { 51 | expression: ` 52 | (() => { 53 | if (__heap__.remove() !== 64) return false 54 | if (JSON.stringify(__heap__.heap) !== '[37,14,32,7,2,10]') 55 | return false 56 | if (__heap__.remove() !== 37) return false 57 | if (JSON.stringify(__heap__.heap) !== '[32,14,10,7,2]') 58 | return false 59 | if (__heap__.remove() !== 32) return false 60 | if (JSON.stringify(__heap__.heap) !== '[14,2,10,7]') 61 | return false 62 | return true 63 | })() 64 | `, 65 | message: 'The remove method removes and returns elements according to the max heap property' 66 | }, 67 | { 68 | expression: `__heap__.__clear__() && __heap__.remove() === null`, 69 | message: 'The remove method returns null when called on an empty heap' 70 | }, 71 | { 72 | expression: `typeof __heap__.sort == 'function'`, 73 | message: 'MaxHeap has a method called sort.' 74 | }, 75 | { 76 | expression: `JSON.stringify(__heap__.sort()) === '[2,7,10,14,32,37,64]'`, 77 | message: 'The sort method returns a sorted array (from least to greatest) containing all the elements in the heap' 78 | }, 79 | { 80 | expression: `typeof __heap__.size == 'function' || typeof __heap__.size == 'number'`, 81 | message: 'MaxHeap has a method or property called size' 82 | }, 83 | { 84 | expression: ` 85 | (() => { 86 | if (typeof __heap__.size === 'undefined') 87 | return false 88 | if (typeof __heap__.size === 'function') { 89 | if (__heap__.size() !== 7) return false 90 | __heap__.insert(64) 91 | __heap__.insert(37) 92 | if (__heap__.size() !== 9) return false 93 | __heap__.remove() 94 | __heap__.remove() 95 | if (__heap__.size() !== 7) return false 96 | } else if (typeof __heap__.size === 'number') { 97 | if (__heap__.size !== 7) return false 98 | __heap__.insert(64) 99 | __heap__.insert(37) 100 | if (__heap__.size !== 9) return false 101 | __heap__.remove() 102 | __heap__.remove() 103 | if (__heap__.size !== 7) return false 104 | } 105 | return true 106 | })()`, 107 | message: 'The size method returns the correct size of the heap' 108 | } 109 | ] 110 | -------------------------------------------------------------------------------- /src/components/ActionMenu.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import Fade from './utils/Fader' 4 | import PropTypes from 'prop-types' 5 | import ReactDOM from 'react-dom' 6 | import ReactTooltip from 'react-tooltip' 7 | import { Share } from 'react-feather' 8 | import { connect } from 'react-redux' 9 | import { openKeyBindingsModal } from '../actions/modal' 10 | 11 | class ActionsMenu extends Component { 12 | openKeyBindingsModal = () => { 13 | this.props.openKeyBindingsModal() 14 | this.props.closeActionsMenu() 15 | } 16 | render() { 17 | const left = parseFloat(this.props.left) 18 | return ReactDOM.createPortal( 19 | 22 |
26 |
31 | 32 |
33 |
36 | Show Key Bindings Modal 37 |
38 | 43 | Get Share Link 44 | 45 |
46 |
, 47 | document.getElementById('action-menu-root') 48 | ) 49 | } 50 | } 51 | 52 | ActionsMenu.propTypes = { 53 | closeActionsMenu: PropTypes.func.isRequired, 54 | generateShareLink: PropTypes.func.isRequired, 55 | left: PropTypes.string.isRequired, 56 | openKeyBindingsModal: PropTypes.func.isRequired, 57 | render: PropTypes.bool.isRequired, 58 | theme: PropTypes.string.isRequired, 59 | } 60 | 61 | const mapStateToProps = (state) => ({ 62 | left: state.panes.leftWidth, 63 | theme: state.theme.current, 64 | }) 65 | 66 | export default connect(mapStateToProps, { openKeyBindingsModal })(ActionsMenu) 67 | -------------------------------------------------------------------------------- /src/components/Modal.js: -------------------------------------------------------------------------------- 1 | import '../styles/modal.css' 2 | 3 | import React, { Component } from 'react' 4 | 5 | import AnnouncementModal from './modals/AnnouncementModal'; 6 | import BindingsModal from './modals/BindingsModal'; 7 | import ConfirmModal from './modals/ConfirmModal'; 8 | import Fade from './utils/Fader' 9 | import PropTypes from 'prop-types' 10 | import ReactDOM from 'react-dom' 11 | import ResourcesModal from './modals/ResourcesModal'; 12 | import ThemeModal from './modals/ThemeModal'; 13 | import { closeModal } from '../actions/modal' 14 | import { connect } from 'react-redux' 15 | import { deleteRepl } from '../actions/editor' 16 | import shortid from 'shortid' 17 | 18 | class Modal extends Component { 19 | componentDidMount() { 20 | document.addEventListener('click', this.closeModal) 21 | } 22 | componentWillUnmount() { 23 | document.removeEventListener('click', this.closeModal) 24 | } 25 | // close modal on click if modal is open, if current click 26 | // is not inside modal, and if target is not a modal trigger, 27 | // (and if target's parent is not a modal trigger — to account 28 | // for clicking the path or line of new SVG icons) 29 | closeModal = ({ target }) => { 30 | if ( 31 | this.props.renderModal && 32 | !this.modal.contains(target) && 33 | !this.targetContainsModalTrigger(target) 34 | ) { 35 | this.props.closeModal() 36 | } 37 | } 38 | // closeModal util, see above 39 | targetContainsModalTrigger = (t) => { 40 | if (( 41 | t.classList && 42 | t.classList.contains('modal-trigger') 43 | ) || ( 44 | t.parentElement.classList && 45 | t.parentElement.classList.contains('modal-trigger') 46 | )) return true 47 | return false 48 | } 49 | deleteRepl = ({ target: { id } }) => { 50 | this.props.deleteRepl(id.slice(8)) 51 | this.props.closeModal() 52 | } 53 | renderListItem = (item, i, arr) => { 54 | if (this.props.modalType === 'resources') { 55 | return ( 56 |
  • 57 | 58 | {item.caption} 59 | 60 |
  • 61 | ) 62 | } 63 | // Announcement Modal: 64 | return i !== arr.length - 1 && ( 65 |
  • 69 | ) 70 | } 71 | render() { 72 | const { renderModal, header, modalType } = this.props 73 | return ReactDOM.createPortal( 74 | this.modal = ref} 76 | in={renderModal} 77 | duration={modalType === 'theme' ? {enter: 50, exit: 100} : 450}> 78 | { modalType === 'announcement' 79 | ? 83 | : modalType === 'bindings' 84 | ? 85 | : modalType === 'resources' 86 | ? 89 | : modalType === 'confirm' 90 | ? 95 | : } 96 | , 97 | document.getElementById('modal-root') 98 | ) 99 | } 100 | } 101 | 102 | Modal.propTypes = { 103 | closeModal: PropTypes.func.isRequired, 104 | header: PropTypes.string.isRequired, 105 | messages: PropTypes.array.isRequired, 106 | modalType: PropTypes.string.isRequired, 107 | renderModal: PropTypes.bool.isRequired, 108 | subHeader: PropTypes.string, 109 | theme: PropTypes.string.isRequired 110 | } 111 | 112 | const mapStateToProps = ({ modal, theme }) => { 113 | return { 114 | header: modal.modalId, 115 | messages: modal.messages, 116 | modalType: modal.modalType, 117 | renderModal: modal.renderModal, 118 | subHeader: modal.subHeader, 119 | theme: theme.current 120 | } 121 | } 122 | 123 | export default connect(mapStateToProps, { closeModal, deleteRepl })(Modal) 124 | -------------------------------------------------------------------------------- /src/components/modals/AnnouncementModal.js: -------------------------------------------------------------------------------- 1 | import { RENDER_MODAL } from '../../utils/localStorageKeys' 2 | import React from 'react' 3 | import { map } from 'lodash' 4 | 5 | const AnnouncementModal = (props) => ( 6 |
    7 |

    8 | { props.header } 9 |

    10 |

    11 | 12 |

    13 |
      14 | { map(props.messages, props.renderListItem) } 15 |
    16 |

    17 | 19 |

    20 | {(() => { 21 | let num = localStorage.getItem(RENDER_MODAL) 22 | num = num === '1' ? 2 : num === '2' ? 1 : 0 23 | return ( 24 | 25 | {`You will see this notification ${num} more time${num === 1 ? '' : 's'}`} 26 | 27 | ) 28 | })()} 29 |
    30 | ) 31 | 32 | export default AnnouncementModal 33 | -------------------------------------------------------------------------------- /src/components/modals/BindingsModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const BindingsModal = (props) => ( 4 |
    5 |

    6 | { props.header } 7 |

    8 |
    9 |
    10 | ) 11 | 12 | export default BindingsModal 13 | -------------------------------------------------------------------------------- /src/components/modals/ConfirmModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { replace } from 'lodash-es' 3 | 4 | const ConfirmModal = ({ closeModal, deleteRepl, id, theme }) => ( 5 |
    6 |

    7 | { `Are you sure you want to delete the "${replace(id, /_/g, ' ')}" repl?` } 8 |

    9 |
    10 |
    14 | Yes 15 |
    16 |
    19 | No 20 |
    21 |
    22 |
    23 | ) 24 | 25 | export default ConfirmModal 26 | -------------------------------------------------------------------------------- /src/components/modals/ResourcesModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { map } from 'lodash-es' 3 | 4 | const ResourcesModal = (props) => ( 5 |
    6 |

    7 | { props.header } 8 |

    9 |
      10 | { map(props.messages, props.renderListItem) } 11 |
    12 |
    13 | ) 14 | 15 | export default ResourcesModal 16 | -------------------------------------------------------------------------------- /src/components/modals/ThemeModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ThemeModal = ({ header }) => ( 4 |

    7 | { header } 8 |

    9 | ) 10 | 11 | export default ThemeModal 12 | -------------------------------------------------------------------------------- /src/components/sidebar/Console.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import '../../styles/console.css' 3 | 4 | import * as React from 'react' 5 | 6 | import { 7 | ICON_BLACK, 8 | ICON_DISABLED, 9 | ICON_WHITE 10 | } from '../../utils/base64' 11 | 12 | import { ERROR_TYPES } from '../../utils/regexp' 13 | import type { State } from '../../types/State' 14 | import { clearConsole } from '../../actions/console' 15 | import { connect } from 'react-redux' 16 | import { map } from 'lodash'; 17 | import shortid from 'shortid' 18 | 19 | type ButtonState = { 20 | background: string, 21 | color: string, 22 | icon: string 23 | } 24 | 25 | type Props = { 26 | bottomHeight: string, 27 | clearConsole: () => Object, 28 | messages: string[], 29 | theme: string, 30 | transition: string, 31 | } 32 | 33 | // button states 34 | const disabled: ButtonState = { 35 | background: '#1b1d1a', 36 | color: '#787577', 37 | icon: ICON_DISABLED 38 | } 39 | 40 | const hover: ButtonState = { 41 | background: '#ccc', 42 | color: '#1b1d1a', 43 | icon: ICON_BLACK 44 | } 45 | 46 | const _default: ButtonState = { 47 | background: '#1b1d1a', 48 | color: '#ccc', 49 | icon: ICON_WHITE 50 | } 51 | 52 | class Console extends React.Component { 53 | state = disabled 54 | componentWillReceiveProps(nextProps) { 55 | if (!nextProps.messages.length) { 56 | this.setState(disabled) 57 | } else { 58 | this.setState(_default) 59 | } 60 | } 61 | handleMouseLeave = () => { 62 | if (!this.props.messages.length) return 63 | this.setState(_default) 64 | } 65 | handleMouseEnter = () => { 66 | if (!this.props.messages.length) return 67 | this.setState(hover) 68 | } 69 | renderMessages = (msg) => { 70 | let className = ERROR_TYPES.test(msg) 71 | ? 'sidebar--output--messages--message error' 72 | : 'sidebar--output--messages--message' 73 | return ( 74 |

    78 | ) 79 | } 80 | render() { 81 | const { background, color, icon } = this.state 82 | const margin = { marginBottom: -4 } 83 | 84 | const buttonStyle = { 85 | background, 86 | color, 87 | outline: `1px solid ${color}`, 88 | userSelect: 'none' 89 | } 90 | 91 | return ( 92 |

    98 |
    101 |
    107 | Clear backspace icon 108 |
    109 |

    110 | {'// console output / tests:'} 111 |

    112 | { map(this.props.messages, this.renderMessages) } 113 |
    114 |
    115 | ) 116 | } 117 | } 118 | 119 | const mapStateToProps = ({ 120 | console: messages, 121 | theme: { 122 | current: theme 123 | }, 124 | panes: { 125 | bottomHeight, 126 | transition 127 | } 128 | }: State) => ({ 129 | messages, 130 | bottomHeight, 131 | transition, 132 | theme 133 | }) 134 | 135 | export default connect(mapStateToProps, { clearConsole })(Console) 136 | -------------------------------------------------------------------------------- /src/components/sidebar/Menu.js: -------------------------------------------------------------------------------- 1 | import '../../styles/menu.css' 2 | 3 | import { isMenuOpen, toggleMenu } from '../../actions/menu' 4 | 5 | import { CODE } from '../../assets/codeRef' 6 | import MenuMap from './MenuMap' 7 | import PropTypes from 'prop-types' 8 | import React from 'react' 9 | import ReplsMap from './ReplsMap' 10 | import { connect } from 'react-redux' 11 | 12 | const { 13 | SORTING_ALGOS, 14 | DATA_STRUCTURES, 15 | EASY_ALGOS, 16 | MODERATE_ALGOS 17 | } = CODE 18 | 19 | class Menu extends React.Component { 20 | toggleMenu = (e) => { 21 | e.stopPropagation() 22 | e.currentTarget.open 23 | ? this.props.toggleMenu({ name: e.currentTarget.id, open: true }) 24 | : this.props.toggleMenu({ name: e.currentTarget.id, open: false }) 25 | } 26 | render() { 27 | const { topHeight: height, transition } = this.props 28 | const isOpen = isMenuOpen(this.props.menuState, 'ALGORITHM_CHALLENGES') 29 | return ( 30 |
    33 |
    34 | Contents 35 |
    36 | 40 | 44 |
    48 | 49 | Algorithm Challenges 50 | 51 | 55 | 60 |
    61 | 62 |
    63 | ) 64 | } 65 | } 66 | 67 | Menu.propTypes = { 68 | theme: PropTypes.string.isRequired, 69 | topHeight: PropTypes.string.isRequired, 70 | transition: PropTypes.string.isRequired, 71 | menuState: PropTypes.arrayOf(PropTypes.object).isRequired, 72 | } 73 | 74 | const mapStateToProps = ({ panes, theme, menu }) => ({ 75 | theme: theme.current, 76 | topHeight: panes.topHeight, 77 | transition: panes.transition, 78 | menuState: menu 79 | }) 80 | 81 | export default connect(mapStateToProps, { toggleMenu })(Menu) 82 | -------------------------------------------------------------------------------- /src/components/sidebar/MenuMap.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { closeModal, openResourcesModal } from '../../actions/modal' 3 | import { connect } from 'react-redux' 4 | import { isMenuOpen, toggleMenu } from '../../actions/menu' 5 | import { selectChallenge, selectSolution } from '../../actions/editor' 6 | import MenuButtons from '../utils/MenuButtons' 7 | import PropTypes from 'prop-types' 8 | import React, { Component } from 'react' 9 | import shortid from 'shortid' 10 | 11 | _.mixin({ 12 | 'pascalCase': _.flow( 13 | _.camelCase, 14 | _.upperFirst 15 | ) 16 | }) 17 | 18 | class MenuMap extends Component { 19 | selectChallenge = ({ currentTarget: { id }}) => { 20 | this.props.selectChallenge(id) 21 | } 22 | // stop event propagation to prevent events 23 | // bound to containing elements from firing 24 | selectSolution = (e) => { 25 | e.stopPropagation() 26 | this.props.selectSolution(e.currentTarget.id.slice(10)) 27 | } 28 | toggleMenu = (e) => { 29 | e.stopPropagation() 30 | e.currentTarget.open 31 | ? this.props.toggleMenu({ name: this.props.name, open: true }) 32 | : this.props.toggleMenu({ name: this.props.name, open: false }) 33 | } 34 | renderModal = (e) => { 35 | e.stopPropagation() 36 | const modalId = _.startCase(e.currentTarget.id.slice(7)) 37 | this.props.modalId === modalId && this.props.renderModal 38 | ? this.props.closeModal() 39 | : this.props.openResourcesModal(modalId) 40 | } 41 | renderMenuItem = (item) => { 42 | const id = _.pascalCase(item.title) 43 | let itemClasses = id === this.props.codeId ? 'active ' : '' 44 | itemClasses += this.props.theme + ' ' + this.props.xtraClass 45 | return ( 46 |
    51 | 52 | {item.title} 53 | 54 | {/* If challenge does not have resources or solution, e.g. 55 | repl or sort benchmarks, do not render button container */} 56 | { item.solution && item.resources && 57 | } 63 |
    64 | ) 65 | } 66 | render() { 67 | const isOpen = isMenuOpen(this.props.menuState, this.props.name) 68 | return ( 69 |
    70 | 71 | {this.props.header} 72 | 73 | { _.map(this.props.items, this.renderMenuItem) } 74 |
    75 | ) 76 | } 77 | } 78 | 79 | MenuMap.propTypes = { 80 | closeModal: PropTypes.func.isRequired, 81 | codeId: PropTypes.string.isRequired, 82 | header: PropTypes.string.isRequired, 83 | items: PropTypes.array.isRequired, 84 | menuState: PropTypes.arrayOf(PropTypes.object).isRequired, 85 | modalId: PropTypes.string.isRequired, 86 | name: PropTypes.string, 87 | openResourcesModal: PropTypes.func.isRequired, 88 | renderModal: PropTypes.bool.isRequired, 89 | selectChallenge: PropTypes.func.isRequired, 90 | selectSolution: PropTypes.func.isRequired, 91 | theme: PropTypes.string.isRequired, 92 | toggleMenu: PropTypes.func.isRequired, 93 | xtraClass: PropTypes.string, 94 | } 95 | 96 | MenuMap.defaultProps = { 97 | xtraClass: '' 98 | } 99 | 100 | const mapStateToProps = (state) => ({ 101 | codeId: state.editor.current.id, 102 | menuState: state.menu, 103 | modalId: state.modal.modalId, 104 | renderModal: state.modal.renderModal, 105 | theme: state.theme.current, 106 | }) 107 | 108 | const mapDispatchToProps = { 109 | closeModal, 110 | openResourcesModal, 111 | selectChallenge, 112 | selectSolution, 113 | toggleMenu 114 | } 115 | 116 | export default connect(mapStateToProps, mapDispatchToProps)(MenuMap) 117 | -------------------------------------------------------------------------------- /src/components/utils/BounceTransition.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Transition from 'react-transition-group/Transition' 3 | 4 | const BounceInBounceOut = ({ children, position, ...props }) => ( 5 | node.classList.add('bounceIn', 'animateToast')} 9 | onExit={node => { 10 | node.classList.remove('bounceIn', 'animateToast'); 11 | node.classList.add('bounceOut', 'animateToast'); 12 | }}> 13 | {children} 14 | 15 | ) 16 | 17 | export default BounceInBounceOut 18 | -------------------------------------------------------------------------------- /src/components/utils/Divider.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | import { HORIZONTAL_GRIP, VERTICAL_GRIP } from '../../utils/base64'; 5 | 6 | const Divider = ({ attachRef, direction }: { attachRef: Function, direction: string }) => { 7 | return ( 8 |
    17 | ) 18 | } 19 | 20 | Divider.propTypes = { 21 | direction: PropTypes.oneOf(['horizontal', 'vertical']), 22 | attachRef: PropTypes.func.isRequired 23 | } 24 | 25 | export default Divider 26 | -------------------------------------------------------------------------------- /src/components/utils/ErrorBoundary.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class ErrorBoundary extends Component { 4 | constructor(props) { 5 | super(props) 6 | this.state = { 7 | hasError: false 8 | } 9 | } 10 | componentDidCatch(error, info) { 11 | console.log(error, info) 12 | this.setState({ hasError: true }) 13 | } 14 | render() { 15 | if (this.state.hasError) { 16 | return

    Whoops! Our bad, it's probably nothing. Try again!

    17 | } 18 | return this.props.children 19 | } 20 | } 21 | 22 | export default ErrorBoundary 23 | -------------------------------------------------------------------------------- /src/components/utils/Fader.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from 'react' 3 | 4 | import { Transition } from 'react-transition-group' 5 | 6 | const transitionStyles: { [string]: Object } = { 7 | entering: { opacity: 0 }, 8 | entered: { opacity: 1 }, 9 | exiting: { opacity: 0 }, 10 | } 11 | 12 | type Props = { 13 | in: boolean, 14 | children: React.Node, 15 | attachRef: Function, 16 | duration: any 17 | } 18 | 19 | const Fade = ({ in: inProp, children, attachRef, duration }: Props) => { 20 | const defaultStyle = { 21 | transition: `opacity ${duration.exit ? duration.exit : duration}ms ease-out`, 22 | opacity: 0 23 | } 24 | return ( 25 | 26 | {(state) => ( 27 |
    31 | {children} 32 |
    33 | )} 34 |
    35 | ) 36 | } 37 | 38 | export default Fade 39 | -------------------------------------------------------------------------------- /src/components/utils/MenuButtons.js: -------------------------------------------------------------------------------- 1 | import { FileText, BookOpen } from 'react-feather' 2 | import { snakeCase } from 'lodash' 3 | import PropTypes from 'prop-types' 4 | import React from 'react' 5 | import ReactTooltip from 'react-tooltip' 6 | 7 | const MenuButtons = ({ id, theme, title, selectSolution, renderModal }) => ( 8 |
    9 | 15 | 20 | Solution 21 | 22 | 28 | 33 | Resources 34 | 35 |
    36 | ) 37 | 38 | MenuButtons.propTypes = { 39 | id: PropTypes.string.isRequired, 40 | theme: PropTypes.string.isRequired, 41 | title: PropTypes.string.isRequired, 42 | selectSolution: PropTypes.func.isRequired, 43 | renderModal: PropTypes.func.isRequired 44 | } 45 | 46 | export default MenuButtons 47 | -------------------------------------------------------------------------------- /src/components/utils/Pane.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import PropTypes from 'prop-types'; 3 | import * as React from 'react'; 4 | import { connect } from 'react-redux'; 5 | 6 | type Props = { 7 | className: string, 8 | theme: string, 9 | leftWidth: string, 10 | rightWidth: string, 11 | children: React.Node 12 | } 13 | 14 | const Pane = ({ 15 | children, 16 | className, 17 | leftWidth, 18 | rightWidth, 19 | theme 20 | }: Props) => ( 21 |
    28 | { children } 29 |
    30 | ) 31 | 32 | Pane.propTypes = { 33 | children: PropTypes.array.isRequired, 34 | className: PropTypes.string.isRequired, 35 | leftWidth: PropTypes.string.isRequired, 36 | rightWidth: PropTypes.string.isRequired, 37 | theme: PropTypes.string.isRequired 38 | } 39 | 40 | const mapStateToProps = ({ panes, theme }) => ({ 41 | leftWidth: panes.leftWidth, 42 | rightWidth: panes.rightWidth, 43 | theme: theme.current 44 | }) 45 | 46 | export default connect(mapStateToProps)(Pane) 47 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { Store } from 'redux'; 5 | import App from './App'; 6 | import ErrorBoundary from './components/utils/ErrorBoundary'; 7 | import configureStore from './store'; 8 | import './styles/index.css'; 9 | import createProxyConsole from './utils/createProxyConsole'; 10 | import * as LS from './utils/localStorageKeys'; 11 | import { DO_NOT_SAVE } from './utils/regexp'; 12 | import registerServiceWorker from './utils/registerServiceWorker'; 13 | import simpleDrag from './utils/simpleDrag'; 14 | import type { 15 | State 16 | } from './types/State' 17 | 18 | // NOTE: set to true or use console.info 19 | // when debugging w/ console.log becomes 20 | // problematic due to proxy console func 21 | export const disableProxyConsole = false 22 | 23 | // intercept console.log messages 24 | createProxyConsole() 25 | 26 | // enable resizable split panes 27 | simpleDrag() 28 | 29 | export const store: Store = configureStore() 30 | 31 | // set localStorage when navigating away from app 32 | window.onbeforeunload = function(e) { 33 | const state = store.getState() 34 | // use // DO NOT SAVE comment to disable saving 35 | if (!DO_NOT_SAVE.test(state.editor.current.code) && !state.editor.isSharedRepl) { 36 | localStorage.setItem( 37 | LS.EDITOR_STATE, 38 | JSON.stringify(state.editor) 39 | ) 40 | localStorage.setItem( 41 | LS.PANES_STATE, 42 | JSON.stringify(state.panes) 43 | ) 44 | localStorage.setItem( 45 | LS.THEME_STATE, 46 | JSON.stringify(state.theme) 47 | ) 48 | localStorage.setItem( 49 | LS.MENU_STATE, 50 | JSON.stringify(state.menu) 51 | ) 52 | } 53 | // save pane state 54 | console.log('CS-Playground-React-State Saved!') 55 | } 56 | 57 | ReactDOM.render( 58 | 59 | 60 | 61 | 62 | , 63 | document.getElementById('root') 64 | ) 65 | 66 | if (module.hot) { 67 | module.hot.accept('./App', () => { 68 | const NextApp = require('./App').default 69 | ReactDOM.render( 70 | 71 | 72 | 73 | 74 | , 75 | document.getElementById('root') 76 | ) 77 | }) 78 | } 79 | 80 | registerServiceWorker() 81 | -------------------------------------------------------------------------------- /src/reducers/console.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Action } from '../types/Actions' 3 | 4 | export default (state: string[] = [], action: Action): string[] => { 5 | switch(action.type) { 6 | case 'CONSOLE_LOG': 7 | return [ 8 | ...state, 9 | action.logs 10 | ] 11 | case 'CLEAR_CONSOLE': 12 | return [] 13 | default: 14 | return state 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/reducers/editor/children/challenges.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { findIndex, indexOf } from 'lodash' 3 | 4 | import type { Action } from '../../../types/Actions' 5 | import type { EditorState } from '../../../types/Reducers' 6 | 7 | export default (state: EditorState, action: Action): EditorState => { 8 | switch(action.type) { 9 | case 'NEXT_CHALLENGE': { 10 | let { orderKey } = state 11 | let i = indexOf(orderKey, state.current.id) 12 | let next = (i === -1 || i === orderKey.length - 1) ? 0 : i+1 13 | next = findIndex(state.codeStore, { id: orderKey[next] }) 14 | return { 15 | ...state, 16 | current: { 17 | id: state.codeStore[next].id, 18 | code: state.codeStore[next].userCode, 19 | isSolution: false 20 | } 21 | } 22 | } 23 | case 'PREV_CHALLENGE': { 24 | let { orderKey } = state 25 | let i = indexOf(orderKey, state.current.id) 26 | let prev = (i <= 0) ? orderKey.length - 1 : i-1 27 | prev = findIndex(state.codeStore, { id: orderKey[prev] }) 28 | return { 29 | ...state, 30 | current: { 31 | id: state.codeStore[prev].id, 32 | code: state.codeStore[prev].userCode, 33 | isSolution: false 34 | } 35 | } 36 | } 37 | case 'SELECT_CHALLENGE': 38 | let idx = findIndex(state.codeStore, { id: action.id }) 39 | return { 40 | ...state, 41 | current: { 42 | id: action.id, 43 | code: state.codeStore[idx].userCode, 44 | isSolution: false 45 | } 46 | } 47 | default: return state 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/reducers/editor/children/repls.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { filter, indexOf } from 'lodash' 3 | 4 | import type { Action } from '../../../types/Actions' 5 | import type { EditorState } from '../../../types/Reducers' 6 | 7 | export default (state: EditorState, action: Action): EditorState => { 8 | switch (action.type) { 9 | case 'LOAD_SHARED_REPL': 10 | return { 11 | ...state, 12 | isSharedRepl: true, 13 | current: { 14 | id: 'sharedRepl', 15 | code: action.code, 16 | isSolution: false 17 | } 18 | } 19 | case 'ADD_REPL': 20 | return { 21 | ...state, 22 | current: { 23 | id: action.id, 24 | code: '', 25 | isSolution: false 26 | }, 27 | orderKey: [ 28 | ...state.orderKey, 29 | action.id 30 | ], 31 | codeStore: [ 32 | ...state.codeStore, 33 | { 34 | id: action.id, 35 | userCode: '' 36 | } 37 | ] 38 | } 39 | case 'DELETE_REPL': 40 | const id = action.id 41 | const prev = indexOf(state.orderKey, id) - 1 42 | return { 43 | ...state, 44 | codeStore: filter(state.codeStore, repl => 45 | repl.id !== id), 46 | orderKey: filter(state.orderKey, repl => 47 | repl !== id), 48 | current: { 49 | id: state.codeStore[prev].id, 50 | code: state.codeStore[prev].userCode, 51 | isSolution: false 52 | } 53 | } 54 | default: return state 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/reducers/editor/children/solutions.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Action } from '../../../types/Actions' 3 | import type { EditorState } from '../../../types/Reducers' 4 | import { SOLUTIONS } from '../../../assets/codeRef' 5 | import editor from '../editor' 6 | 7 | export default (state: EditorState, action: Action): EditorState => { 8 | switch (action.type) { 9 | case 'SELECT_SOLUTION': 10 | return { 11 | ...state, 12 | current: { 13 | id: action.id, 14 | code: SOLUTIONS.get(action.id) || '', 15 | isSolution: true 16 | } 17 | } 18 | case 'TOGGLE_SOLUTION': 19 | if (!SOLUTIONS.has(state.current.id)) 20 | return state 21 | return !state.current.isSolution 22 | ? editor(state, { type: 'SELECT_SOLUTION', id: state.current.id }) 23 | : editor(state, { type: 'SELECT_CHALLENGE', id: state.current.id }) 24 | default: return state 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/reducers/editor/editor.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { CODE } from '../../assets/codeRef'; 3 | import WELCOME_MESSAGE from '../../assets/seed/welcome'; 4 | import { ALL_TESTS_SUPPRESSED, EDITOR_STATE } from '../../utils/localStorageKeys'; 5 | import challengeNav from './children/challenges'; 6 | import repls from './children/repls'; 7 | import solutionNav from './children/solutions'; 8 | import createOrderKey from './utils/createOrderKey'; 9 | import { runInitializationUtils } from './utils/initializationUtils'; 10 | import populateCodeStore from './utils/populateCodeStore'; 11 | import updateCodeStore from './utils/updateCodeStore'; 12 | 13 | import type { Action } from '../../types/Actions' 14 | import type { EditorState } from '../../types/Reducers' 15 | 16 | const initialState: EditorState = { 17 | isSharedRepl: false, 18 | current: { 19 | id: 'welcome', 20 | code: WELCOME_MESSAGE, 21 | isSolution: false 22 | }, 23 | codeStore: populateCodeStore(CODE), 24 | orderKey: createOrderKey(CODE) 25 | } 26 | 27 | // reducer's default state is either initialState 28 | // or rehydrated from LS, which is set in index.js 29 | const defaultState = runInitializationUtils( 30 | initialState, 31 | localStorage.getItem(EDITOR_STATE), 32 | CODE 33 | ) 34 | 35 | const editor = (state: EditorState = defaultState, action: Action): EditorState => { 36 | switch(action.type) { 37 | case 'ADD_REPL': 38 | case 'DELETE_REPL': 39 | case 'LOAD_SHARED_REPL': 40 | return repls(state, action) 41 | case 'NEXT_CHALLENGE': 42 | case 'PREV_CHALLENGE': 43 | case 'SELECT_CHALLENGE': 44 | return challengeNav(state, action) 45 | case 'SELECT_SOLUTION': 46 | case 'TOGGLE_SOLUTION': 47 | return solutionNav(state, action) 48 | case 'RESET_STATE': 49 | localStorage.removeItem(EDITOR_STATE) 50 | localStorage.removeItem(ALL_TESTS_SUPPRESSED) 51 | return initialState 52 | case 'UPDATE_CODE': 53 | return { 54 | ...state, 55 | codeStore: updateCodeStore(state, action.code), 56 | current: { 57 | ...state.current, 58 | code: action.code 59 | } 60 | } 61 | default: return state 62 | } 63 | } 64 | 65 | export default editor 66 | -------------------------------------------------------------------------------- /src/reducers/editor/utils/createOrderKey.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { flatten, replace } from 'lodash' 3 | 4 | import type { Code } from '../../../assets/codeRef' 5 | 6 | // => arr of chal IDs in correct order 7 | export default function createOrderKey(CODE: Code): string[] { 8 | const { 9 | SORTING_ALGOS, 10 | DATA_STRUCTURES, 11 | EASY_ALGOS, 12 | MODERATE_ALGOS, 13 | REPLS 14 | } = CODE 15 | return flatten([ 16 | SORTING_ALGOS, 17 | DATA_STRUCTURES, 18 | EASY_ALGOS, 19 | MODERATE_ALGOS, 20 | REPLS 21 | ]) 22 | .map(challenge => 23 | replace( 24 | challenge.title, /\s/g, '' 25 | )) 26 | } 27 | -------------------------------------------------------------------------------- /src/reducers/editor/utils/populateCodeStore.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { forEach, replace } from 'lodash' 3 | 4 | import type { Challenge } from '../../../assets/codeRef' 5 | import type { Code } from '../../../assets/codeRef' 6 | import type { CodeStore } from '../../../types/Reducers' 7 | 8 | // codeStore initialization utility 9 | export default function populateCodeStore(CODE: Code): CodeStore { 10 | const arr: CodeStore = [] 11 | for (let category in CODE) { 12 | forEach( 13 | CODE[category], 14 | (challenge: Challenge): void => { 15 | arr.push({ 16 | id: replace(challenge.title, /\s/g, ''), 17 | userCode: challenge.seed 18 | }) 19 | } 20 | ) 21 | } 22 | return arr 23 | } 24 | -------------------------------------------------------------------------------- /src/reducers/editor/utils/updateCodeStore.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { CodeStore, EditorState } from '../../../types/Reducers' 3 | 4 | import { map } from 'lodash' 5 | 6 | export default (state: EditorState, newCode: string): CodeStore => { 7 | if (!state.current.isSolution && state.current.id !== 'welcome') { 8 | return map(state.codeStore, challenge => { 9 | if (state.current.id === challenge.id) { 10 | return { 11 | ...challenge, 12 | userCode: newCode 13 | } 14 | } else { 15 | return challenge 16 | } 17 | }) 18 | } else { 19 | return state.codeStore 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/reducers/menu.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Action } from '../types/Actions' 3 | import { MENU_STATE } from '../utils/localStorageKeys' 4 | import type { MenuState } from '../types/Reducers' 5 | 6 | const initialState: MenuState = [ 7 | { name: 'SORTING_ALGOS', open: true }, 8 | { name: 'DATA_STRUCTURES', open: false }, 9 | { name: 'ALGORITHM_CHALLENGES', open: true }, 10 | { name: 'EASY_ALGOS', open: false }, 11 | { name: 'MODERATE_ALGOS', open: false }, 12 | { name: 'REPLS', open: true } 13 | ] 14 | 15 | // reducer's default state is either initialState 16 | // or rehydrated from LS, which is set in index.js 17 | const hydrate = localStorage.getItem(MENU_STATE) 18 | const defaultState: MenuState = hydrate 19 | ? JSON.parse(hydrate) 20 | : initialState 21 | 22 | export default (state: MenuState = defaultState, action: Action): MenuState => { 23 | switch (action.type) { 24 | case 'TOGGLE_MENU': 25 | const open = action.data.open 26 | const name = action.data.name 27 | return state.map(menuItem =>{ 28 | if (menuItem.name === name) { 29 | return { name: menuItem.name, open } 30 | } 31 | return menuItem 32 | }) 33 | default: 34 | return state 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/reducers/modal.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Action } from '../types/Actions' 3 | import { CODE } from '../assets/codeRef' 4 | import type { ModalState } from '../types/Reducers' 5 | 6 | const defaultState: ModalState = { 7 | renderModal: false, 8 | modalId: '', 9 | messages: [], 10 | subHeader: '', 11 | modalType: '' 12 | } 13 | 14 | export default (state: ModalState = defaultState, action: Action): ModalState => { 15 | switch (action.type) { 16 | case 'CLOSE_MODAL': 17 | return { 18 | ...state, 19 | renderModal: false 20 | } 21 | case 'OPEN_THEME_MODAL': 22 | return { 23 | renderModal: true, 24 | modalId: action.id, 25 | messages: [], 26 | subHeader: '', 27 | modalType: 'theme' 28 | } 29 | case 'OPEN_RESOURCES_MODAL': 30 | // only toggle rederModal if 31 | // modal state already loaded 32 | if (state.modalId === action.id) { 33 | return { 34 | ...state, 35 | renderModal: true 36 | } 37 | } 38 | // otherwise find the right 39 | // modal and load it's state 40 | let newState = state 41 | for (let category in CODE) { 42 | for (let topic of CODE[category]) { 43 | if (topic.title === action.id) { 44 | newState = { 45 | modalId: action.id, 46 | modalType: 'resources', 47 | renderModal: true, 48 | messages: topic.resources 49 | } 50 | } 51 | } 52 | } 53 | return newState 54 | case 'OPEN_KEY_BINDINGS_MODAL': 55 | return { 56 | modalId: action.id, 57 | modalType: 'bindings', 58 | renderModal: true, 59 | messages: action.messages 60 | } 61 | case 'OPEN_ANNOUNCEMENT_MODAL': 62 | return { 63 | modalId: action.id, 64 | modalType: 'announcement', 65 | renderModal: true, 66 | subHeader: action.subHeader, 67 | messages: action.messages 68 | } 69 | case 'OPEN_CONFIRM_MODAL': 70 | return { 71 | modalId: action.id, 72 | modalType: 'confirm', 73 | renderModal: true, 74 | subHeader: '', 75 | messages: [] 76 | } 77 | default: return state 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/reducers/panes.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Action } from '../types/Actions' 3 | import { PANES_STATE } from '../utils/localStorageKeys'; 4 | import type { PanesState } from '../types/Reducers' 5 | 6 | const initialState: PanesState = { 7 | topHeight: '70%', 8 | bottomHeight: '30%', 9 | leftWidth: '30%', 10 | rightWidth: '69.5%', 11 | transition: 'none', 12 | clickState: 0 13 | } 14 | 15 | // reducer's default state is either initialState 16 | // or rehydrated from LS, which is set in index.js 17 | const hydrate = localStorage.getItem(PANES_STATE) 18 | const defaultState: PanesState = hydrate 19 | ? JSON.parse(hydrate) 20 | : initialState 21 | 22 | const hidePanes = (state: PanesState): PanesState => { 23 | switch (state.clickState) { 24 | case 0: 25 | return { 26 | ...state, 27 | topHeight: '0%', 28 | bottomHeight: '99%', 29 | transition: '.5s', 30 | clickState: 1 31 | } 32 | case 1: 33 | return { 34 | ...state, 35 | topHeight: '99%', 36 | bottomHeight: '0%', 37 | transition: '.5s', 38 | clickState: 2 39 | } 40 | default: 41 | return { 42 | ...state, 43 | topHeight: '70%', 44 | bottomHeight: '30%', 45 | transition: '.5s', 46 | clickState: 0 47 | } 48 | } 49 | } 50 | 51 | export default (state: PanesState = defaultState, action: Action): PanesState => { 52 | switch (action.type) { 53 | case 'RESET_STATE': 54 | localStorage.removeItem(PANES_STATE) 55 | return initialState 56 | case 'DRAG_HORIZONTAL': 57 | return { 58 | ...state, 59 | leftWidth: action.leftWidth, 60 | rightWidth: action.rightWidth, 61 | transition: 'none' 62 | } 63 | case 'DRAG_VERTICAL': 64 | return { 65 | ...state, 66 | topHeight: action.topHeight, 67 | bottomHeight: action.bottomHeight, 68 | transition: 'none' 69 | } 70 | case 'DOUBLE_CLICK': 71 | return hidePanes(state) 72 | default: return state 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/reducers/rootReducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import console from './console' 3 | import editor from './editor/editor' 4 | import menu from './menu' 5 | import modal from './modal' 6 | import panes from './panes' 7 | import theme from './theme' 8 | 9 | const reducers = { 10 | console, 11 | editor, 12 | modal, 13 | panes, 14 | theme, 15 | menu 16 | } 17 | 18 | export type Reducers = typeof reducers 19 | export default combineReducers(reducers) 20 | -------------------------------------------------------------------------------- /src/reducers/theme.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { head, indexOf, isEqual, last } from 'lodash' 3 | 4 | import type { Action } from '../types/Actions' 5 | import { THEME_STATE } from '../utils/localStorageKeys' 6 | import type { ThemeState } from '../types/Reducers' 7 | 8 | const initialState: ThemeState = { 9 | current: 'tomorrow-night-eighties', 10 | themes: [ 11 | 'tomorrow-night-eighties', 12 | 'abcdef', 13 | 'base16-dark', 14 | 'base16-light', // light 15 | 'cobalt', 16 | 'eclipse', // light 17 | 'icecoder', 18 | 'lesser-dark', 19 | 'material', 20 | 'monokai', 21 | 'neat', // light 22 | 'panda-syntax', 23 | 'paraiso-light', // light 24 | 'twilight' 25 | ] 26 | } 27 | 28 | // reducer's default state is either initialState 29 | // or rehydrated from LS, which is set in index.js 30 | let hydrate = localStorage.getItem(THEME_STATE) 31 | let defaultState: ThemeState = hydrate 32 | ? JSON.parse(hydrate) 33 | : initialState 34 | 35 | if (!isEqual(defaultState.themes, initialState.themes)) { 36 | defaultState = { 37 | ...defaultState, 38 | themes: initialState.themes 39 | } 40 | } 41 | 42 | export default (state: ThemeState = defaultState, action: Action): ThemeState => { 43 | switch (action.type) { 44 | case 'NEXT_THEME': { 45 | const idx = state.current === last(state.themes) 46 | ? 0 47 | : indexOf(state.themes, state.current)+1 48 | return { 49 | ...state, 50 | current: state.themes[idx] 51 | } 52 | } 53 | case 'PREV_THEME': { 54 | const idx = state.current === head(state.themes) 55 | ? state.themes.length-1 56 | : indexOf(state.themes, state.current)-1 57 | return { 58 | ...state, 59 | current: state.themes[idx] 60 | } 61 | } 62 | default: 63 | return state 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { applyMiddleware, createStore } from 'redux' 3 | 4 | import type { Action } from './types/Actions' 5 | import type { Store as ReduxStore } from 'redux' 6 | import type { State } from './types/State' 7 | import { composeWithDevTools } from 'redux-devtools-extension'; 8 | import rootReducer from './reducers/rootReducer' 9 | 10 | export type Store = ReduxStore 11 | 12 | export default function configureStore(): Store { 13 | return createStore( 14 | rootReducer, 15 | composeWithDevTools( 16 | applyMiddleware() 17 | ) 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/styles/app.css: -------------------------------------------------------------------------------- 1 | .main.right-pane { 2 | height: 100vh; 3 | } 4 | 5 | .sidebar.left-pane { 6 | top: 0; 7 | left: 0; 8 | box-sizing: border-box; 9 | height: 100vh; 10 | flex: auto; 11 | display: flex; 12 | flex-direction: column; 13 | color: #ccc; 14 | } 15 | 16 | .divider { 17 | position: relative; 18 | background-color: #a19c9c; 19 | background-repeat: no-repeat; 20 | background-position: 50%; 21 | transition: background-color .3s; 22 | } 23 | 24 | .divider:hover { 25 | background-color: #279198; 26 | } 27 | 28 | .divider:active { 29 | background-color: #10656b; 30 | } 31 | 32 | .vertical.divider { 33 | width: 7px; 34 | cursor: col-resize; 35 | } 36 | 37 | .horizontal.divider { 38 | height: 7px; 39 | cursor: row-resize; 40 | } 41 | 42 | /* Toast Animations */ 43 | @keyframes bounceIn { 44 | 0% { 45 | opacity: 0; 46 | transform: translate3d(3000px, 0, 0); 47 | } 48 | 60% { 49 | opacity: 1; 50 | transform: translate3d(-25px, 0, 0); 51 | } 52 | 75% { 53 | transform: translate3d(10px, 0, 0); 54 | } 55 | 90% { 56 | transform: translate3d(-5px, 0, 0); 57 | } 58 | 100% { 59 | transform: translate3d(0, 0, 0); 60 | } 61 | } 62 | 63 | .bounceIn { 64 | animation-name: bounceIn; 65 | } 66 | 67 | @keyframes bounceOut { 68 | 0% { 69 | transform: translate3d(0, 0, 0); 70 | } 71 | 30% { 72 | opacity: 1; 73 | transform: translate3d(-25px, 0, 0); 74 | } 75 | 100% { 76 | opacity: 0; 77 | transform: translate3d(3000px, 0, 0); 78 | } 79 | } 80 | 81 | .bounceOut { 82 | animation-name: bounceOut; 83 | } 84 | 85 | .animateToast { 86 | animation-duration: 800ms; 87 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 88 | } 89 | 90 | @keyframes infiniteSpin { 91 | from { 92 | transform: rotate(0deg); 93 | } 94 | to { 95 | transform: rotate(360deg); 96 | } 97 | } 98 | 99 | .loader-container { 100 | align-items: center; 101 | background: #000000; 102 | display: flex; 103 | height: calc(100vh - 50px); 104 | justify-content: center; 105 | } 106 | 107 | .loader { 108 | animation: infiniteSpin 1000ms infinite; 109 | color: #CCC; 110 | } 111 | -------------------------------------------------------------------------------- /src/styles/codemirror.css: -------------------------------------------------------------------------------- 1 | /* NOTE: 2 | * styles here apply to the codemirror editor itself 3 | * not to the surrounding UI. rules here are meant to 4 | * override default codemirror rules, any theme specific 5 | * styles should go in the respective themes.css file 6 | */ 7 | 8 | .react-codemirror2 { 9 | height: calc(100vh - 50px); 10 | margin: none; 11 | } 12 | 13 | .CodeMirror { 14 | height: 100% !important; 15 | } 16 | 17 | .CodeMirror-gutters { 18 | z-index: 0 !important; 19 | } 20 | 21 | .CodeMirror .cm-matchhighlight { 22 | background: rgba(222, 153, 174, 0.3); 23 | } 24 | 25 | .CodeMirror div.CodeMirror-cursor { 26 | border-left: 1px solid #d5d5d5 !important; 27 | } 28 | 29 | .cm-s-tomorrow-night-eighties .CodeMirror-activeline-background { 30 | background: rgba(187, 141, 245, 0.1) !important; 31 | } 32 | 33 | .CodeMirror-scrollbar-filler { 34 | background: black !important; 35 | } 36 | 37 | .CodeMirror.cm-s-panda-syntax { 38 | font-size: 13px !important; 39 | line-height: 1.3 !important; 40 | } 41 | 42 | .CodeMirror-scrollbar-filler { 43 | background: transparent !important; 44 | } 45 | -------------------------------------------------------------------------------- /src/styles/console.css: -------------------------------------------------------------------------------- 1 | .sidebar--output.bottom-pane { 2 | margin: 0; 3 | position: relative; 4 | } 5 | 6 | .sidebar--output--messages { 7 | background: #1b1d1a; 8 | box-sizing: border-box; 9 | color: #07bb04; 10 | font-family: monospace; 11 | height: 100%; 12 | overflow-y: auto; 13 | } 14 | 15 | .sidebar--output--clear-console { 16 | cursor: pointer; 17 | font-size: 14px; 18 | padding: 4px 7px 6px 7px; 19 | position: absolute; 20 | top: 10px; 21 | right: 18px; 22 | } 23 | 24 | .sidebar--output--messages--message.error { 25 | color: #ff0000; 26 | } 27 | 28 | .sidebar--output--messages--message { 29 | margin: 0; 30 | padding: 5px 0 0 8px; 31 | white-space: pre; 32 | } 33 | 34 | .sidebar--output--messages--message:last-of-type { 35 | margin-bottom: 5px; 36 | } 37 | 38 | .sidebar--output--messages--default-message { 39 | padding: 5px 0 0 8px; 40 | color: #787577; 41 | } 42 | 43 | .type { 44 | color: rgb(241, 246, 9); 45 | } 46 | 47 | .sidebar--output--messages code { 48 | color: #787577; 49 | font-style: italic; 50 | } 51 | 52 | a { 53 | color: #248064; 54 | transition: .5s; 55 | } 56 | 57 | a:hover { 58 | color: #46bc99; 59 | } 60 | -------------------------------------------------------------------------------- /src/styles/controls.css: -------------------------------------------------------------------------------- 1 | .main--controls { 2 | height: 50px; 3 | background: #707070; 4 | display: flex; 5 | } 6 | 7 | .main--controls--button { 8 | display: inline-block; 9 | box-sizing: border-box; 10 | border: none; 11 | border-radius: 0; 12 | border-right: 2px solid black; 13 | background: #707070; 14 | color: #ccc; 15 | height: 100%; 16 | margin: 0; 17 | outline: none; 18 | cursor: pointer; 19 | font-size: 14px; 20 | transition: .2s; 21 | } 22 | 23 | .main--controls--button:hover { 24 | background: rgba(255,255,255,0.1); 25 | } 26 | 27 | .main--controls--button:active { 28 | background: rgba(255,255,255,0.05); 29 | } 30 | 31 | .main--controls--button.run-code { 32 | width: 40%; 33 | } 34 | 35 | .main--controls--button.menu-button { 36 | width: 10%; 37 | } 38 | 39 | .main--controls--action-menu--item { 40 | height: 50px; 41 | display: flex; 42 | align-items: center; 43 | justify-content: center; 44 | background: #707070; 45 | color: #ccc; 46 | transition: background .2s; 47 | } 48 | 49 | .main--controls--action-menu--item:hover { 50 | background: #808080; 51 | } 52 | 53 | #action-menu-root { 54 | margin: 0; 55 | padding: 0; 56 | background: #707070; 57 | } 58 | 59 | .main--controls--action-menu { 60 | width: 200px; 61 | height: 100px; 62 | bottom: 150px; 63 | position: relative; 64 | z-index: 5; 65 | cursor: pointer; 66 | font-size: 14px; 67 | } 68 | 69 | .main--controls--button.next, 70 | .main--controls--button.previous { 71 | width: 25%; 72 | } 73 | 74 | #dummy-input { 75 | position: absolute; 76 | bottom: -500px; 77 | } 78 | -------------------------------------------------------------------------------- /src/styles/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | /* font-smoothing */ 3 | font-size: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | font-variant-ligatures: none; 6 | -webkit-font-variant-ligatures: none; 7 | text-rendering: optimizeLegibility; 8 | -moz-osx-font-smoothing: grayscale; 9 | font-smoothing: antialiased; 10 | -webkit-font-smoothing: antialiased; 11 | text-shadow: rgba(0, 0, 0, .01) 0 0 1px; 12 | } 13 | 14 | body { 15 | font-family: Ubuntu; 16 | margin: 0; 17 | position: relative; 18 | overflow-y: hidden; 19 | } 20 | 21 | #root { 22 | display: flex; 23 | height: 100vh; 24 | width: 100%; 25 | } 26 | 27 | #modal-root { 28 | left: 50%; 29 | position: absolute; 30 | top: 50%; 31 | transform: translate(-50%, -50%); 32 | } 33 | 34 | ::-webkit-scrollbar { 35 | height: 10px; 36 | width: 12px; 37 | } 38 | 39 | ::-webkit-scrollbar-track { 40 | border-radius: 5px; 41 | box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 42 | } 43 | 44 | ::-webkit-scrollbar-thumb { 45 | border-radius: 5px; 46 | box-shadow: inset 0 0 15px rgba(0,0,0,0.5); 47 | } 48 | 49 | ::-webkit-scrollbar-corner { 50 | box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 51 | } 52 | -------------------------------------------------------------------------------- /src/styles/menu.css: -------------------------------------------------------------------------------- 1 | .sidebar--menu summary::-webkit-details-marker { 2 | color: #ccc; 3 | } 4 | 5 | .sidebar--menu.top-pane { 6 | overflow-y: auto; 7 | flex: auto; 8 | user-select: none; 9 | display: flex; 10 | flex-direction: column; 11 | align-items: center; 12 | } 13 | 14 | .sidebar--menu--header { 15 | cursor: default; 16 | text-align: center; 17 | width: 100%; 18 | padding: 15px 0px; 19 | } 20 | 21 | .sidebar--menu--header, 22 | .sidebar--menu--sub-header, 23 | .sidebar--menu--detail { 24 | cursor: pointer; 25 | outline: none; 26 | } 27 | 28 | .sidebar--menu--header, 29 | .sidebar--menu--sub-header { 30 | font-size: 20px; 31 | } 32 | 33 | .sidebar--menu--sub-header { 34 | align-items: center; 35 | background: rgba(255,255,255,0.05); 36 | display: flex; 37 | flex-direction: row-reverse; 38 | justify-content: space-between; 39 | padding: 15px; 40 | transition: all 300ms; 41 | } 42 | 43 | .sidebar--menu--sub-header:hover, 44 | .sidebar--menu--detail:hover { 45 | background: rgba(255,255,255,0.1); 46 | } 47 | 48 | .sidebar--menu--detail.active { 49 | background: rgba(255,255,255,0.05); 50 | } 51 | 52 | @-moz-document url-prefix() { 53 | .sidebar--menu--sub-header { 54 | flex-direction: row; 55 | } 56 | } 57 | 58 | .sidebar--menu--sub-header.sub, 59 | .sidebar--menu--detail.sub { 60 | padding-left: 20px; 61 | } 62 | 63 | .sidebar--menu--detail--button--container { 64 | display: flex; 65 | } 66 | 67 | .sidebar.left-pane, 68 | .sidebar--menu--detail { 69 | background: #707070; 70 | } 71 | 72 | .sidebar--menu--detail { 73 | font-size: 15px; 74 | padding: 5px 5px 5px 20px; 75 | margin: 0; 76 | transition: .2s !important; 77 | overflow-x: hidden; 78 | display: flex; 79 | justify-content: space-between; 80 | align-items: center; 81 | min-height: 38px; /* for menu details without icons */ 82 | } 83 | 84 | .sidebar--menu--detail > span { 85 | max-width: 40%; /* prevent long words like "checkerboard" from altering icons */ 86 | } 87 | 88 | .sidebar--menu--detail--button { 89 | margin: 5px 0 5px 10px; 90 | padding: 2px 5px 2px 5px; 91 | font-size: 13px; 92 | color: #3e3e3e; 93 | } 94 | 95 | .sidebar--menu--detail--button.resources { 96 | margin-right: 5px; 97 | } 98 | 99 | .sidebar--menu details { 100 | margin-top: 15px; 101 | } 102 | 103 | .sidebar--menu > details:first-of-type { 104 | margin-top: 0; 105 | } 106 | 107 | .sidebar--menu > details:last-of-type { 108 | margin-bottom: 20px; 109 | } 110 | 111 | .sidebar--menu > details { 112 | width: 96%; 113 | } 114 | 115 | .sidebar--menu > details > details { 116 | padding-left: 20px; 117 | } 118 | 119 | .sidebar--menu--detail form { 120 | width: 100%; 121 | margin-left: 10px; 122 | } 123 | 124 | .sidebar--menu--input { 125 | font-size: 15px; 126 | width: 90%; 127 | outline: none; 128 | border: none; 129 | background: none; 130 | color: #ccc; 131 | } 132 | 133 | .sidebar--menu--input-icon { 134 | color: #ccc; 135 | } 136 | -------------------------------------------------------------------------------- /src/styles/modal.css: -------------------------------------------------------------------------------- 1 | #modal-root { 2 | z-index: 4; 3 | } 4 | 5 | .modal { 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | background: #5b5b5b; 10 | color: #ccc; 11 | padding: 2px 50px 30px 50px; 12 | border-radius: 10px; 13 | transition: .4s; 14 | box-shadow: inset 0 0 2px black, 10px 1px 1px 1000px rgba(0, 0, 0, 0.5); 15 | } 16 | 17 | .modal li { 18 | margin: 5px 0 5px 0; 19 | } 20 | 21 | .modal h2 { 22 | margin-bottom: 0; 23 | } 24 | 25 | .modal a, 26 | .modal span { 27 | text-decoration: none; 28 | color: #46bc99; 29 | } 30 | 31 | .modal code { 32 | color: rgb(26, 25, 27); 33 | font-style: italic; 34 | } 35 | 36 | .modal kbd { 37 | display: inline-block; 38 | padding: 3px 5px; 39 | font-size: 11px; 40 | line-height: 10px; 41 | color: #444d56; 42 | vertical-align: middle; 43 | background-color: #fafbfc; 44 | border: solid 1px #c6cbd1; 45 | border-bottom-color: #959da5; 46 | border-radius: 3px; 47 | box-shadow: inset 0 -1px 0 #959da5; 48 | } 49 | 50 | .modal a:hover { 51 | text-decoration: underline; 52 | color: #99cc99; 53 | } 54 | 55 | .modal--header { 56 | text-align: center; 57 | user-select: none; 58 | max-width: 400px; 59 | } 60 | 61 | .modal--confirm-buttons { 62 | display: flex; 63 | flex-direction: row; 64 | align-items: center; 65 | justify-content: center; 66 | height: 80px; 67 | margin-bottom: -20px; 68 | } 69 | 70 | .modal--confirm--yes, 71 | .modal--confirm--no { 72 | padding: 7px 25px; 73 | border: 2px solid #ccc; 74 | margin: 5px; 75 | border-radius: 8px; 76 | cursor: pointer; 77 | transition: .2s; 78 | } 79 | 80 | .modal--confirm--yes:hover, 81 | .modal--confirm--no:hover { 82 | border-color: #46bc99; 83 | color: #99cc99; 84 | } 85 | 86 | .modal--confirm--yes:active, 87 | .modal--confirm--no:active { 88 | border-color: #99cc99; 89 | color: #46bc99; 90 | } 91 | -------------------------------------------------------------------------------- /src/styles/themes/__index__.css: -------------------------------------------------------------------------------- 1 | /* NOTE: 2 | * styles here DO NOT apply to the codemirror editor itself 3 | * these rules are style extensions of the built in CodeMirror 4 | * themes, in order to make the surrounding UI match the editor. 5 | * the following elements have the dynamic theme classname which 6 | * changes along with the current theme and can be styled accordingly: 7 | * .sidebar--menu. > details 8 | * .sidebar.left-pane. 9 | * .sidebar--menu--detail. 10 | * .sidebar--menu--sub-header. 11 | * .matn--controls. 12 | * .main--controls--button. 13 | * .sidebar--output--messages. 14 | * .sidebar--menu--detail--button.solution. 15 | * .sidebar--menu--detail--button.resources. 16 | * .loader-container. 17 | * .loader. 18 | * .toast. 19 | * .sidebar--menu--input. 20 | * .sidebar--menu--input-icon. 21 | * .modal--confirm--yes. 22 | * .modal--confirm--no. 23 | */ 24 | 25 | @import 'abcdef.css'; 26 | @import 'base16-dark.css'; 27 | @import 'base16-light.css'; 28 | @import 'cobalt.css'; 29 | @import 'eclipse.css'; 30 | @import 'icecoder.css'; 31 | @import 'lesser-dark.css'; 32 | @import 'material.css'; 33 | @import 'monokai.css'; 34 | @import 'neat.css'; 35 | @import 'panda-syntax.css'; 36 | @import 'paraiso-light.css'; 37 | @import 'tomorrow-night-eighties.css'; 38 | @import 'twilight.css'; 39 | -------------------------------------------------------------------------------- /src/styles/themes/abcdef.css: -------------------------------------------------------------------------------- 1 | .sidebar--menu--detail--button.solution.abcdef { 2 | color: darkgoldenrod; 3 | } 4 | 5 | .loader.abcdef, 6 | .sidebar--menu--detail--button.resources.abcdef { 7 | color: #abcdef; 8 | } 9 | 10 | .sidebar--menu.abcdef > details { 11 | width: 92%; 12 | } 13 | 14 | .loader-container.abcdef { 15 | background: #0f0f0f; 16 | } 17 | 18 | .sidebar--menu--input.abcdef:focus { 19 | border-bottom: 2px solid darkgoldenrod; 20 | } 21 | 22 | .toast.abcdef { 23 | background: darkgoldenrod; 24 | } 25 | 26 | .modal--confirm--yes.abcdef:hover, 27 | .modal--confirm--no.abcdef:hover { 28 | border-color: darkgoldenrod; 29 | color: #abcdef; 30 | } 31 | 32 | .modal--confirm--yes.abcdef:active, 33 | .modal--confirm--no.abcdef:active { 34 | border-color: #abcdef; 35 | color: darkgoldenrod; 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/themes/base16-dark.css: -------------------------------------------------------------------------------- 1 | .sidebar--menu--detail--button.solution.base16-dark { 2 | color: #ac4142; 3 | } 4 | 5 | .loader.base16-dark, 6 | .sidebar--menu--detail--button.resources.base16-dark { 7 | color: #90a959; 8 | } 9 | 10 | .loader-container.base16-dark { 11 | background: #151515; 12 | } 13 | 14 | .sidebar--menu--input.base16-dark:focus { 15 | border-bottom: 2px solid #ac4142; 16 | } 17 | 18 | .toast.base16-dark { 19 | background: #ac4142; 20 | } 21 | 22 | .modal--confirm--yes.base16-dark:hover, 23 | .modal--confirm--no.base16-dark:hover { 24 | border-color: #90a959; 25 | color: #151515; 26 | } 27 | 28 | .modal--confirm--yes.base16-dark:active, 29 | .modal--confirm--no.base16-dark:active { 30 | border-color: #151515; 31 | color: #90a959; 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/themes/base16-light.css: -------------------------------------------------------------------------------- 1 | .sidebar.left-pane.base16-light, 2 | .sidebar--menu--detail.base16-light { 3 | background: #f5f5f5; 4 | color: #8f5536; 5 | } 6 | 7 | .loader.base16-light, 8 | .sidebar--menu--detail--button.solution.base16-light { 9 | color: #ac4142; 10 | } 11 | 12 | .sidebar--menu--detail--button.resources.base16-light { 13 | color: #90a959; 14 | } 15 | 16 | .CodeMirror.cm-s-base16-light div.CodeMirror-cursor { 17 | border-left: 1px solid #a22 !important; 18 | } 19 | 20 | .sidebar--menu--sub-header.base16-light:hover, 21 | .sidebar--menu--detail.base16-light:hover { 22 | background: rgba(0,0,0,0.08); 23 | } 24 | 25 | .sidebar--menu--detail.base16-light.active { 26 | background: rgba(0,0,0,0.03); 27 | } 28 | 29 | .loader-container.base16-light { 30 | background: #f5f5f5; 31 | } 32 | 33 | .toast.base16-light { 34 | background: #ac4142; 35 | } 36 | 37 | .sidebar--menu--input.base16-light, 38 | .sidebar--menu--input-icon.base16-light { 39 | color: #8f5536; 40 | } 41 | 42 | .sidebar--menu--input.base16-light:focus { 43 | border-bottom: 2px solid #f4bf75; 44 | } 45 | 46 | .modal--confirm--yes.base16-light:hover, 47 | .modal--confirm--no.base16-light:hover { 48 | border-color: #f4bf75; 49 | color: #90a959; 50 | } 51 | 52 | .modal--confirm--yes.base16-light:active, 53 | .modal--confirm--no.base16-light:active { 54 | border-color: #90a959; 55 | color: #f4bf75; 56 | } 57 | -------------------------------------------------------------------------------- /src/styles/themes/cobalt.css: -------------------------------------------------------------------------------- 1 | .sidebar.left-pane.cobalt, 2 | .sidebar--menu--detail.cobalt, 3 | .main--controls.cobalt { 4 | background: #002240; 5 | color: white; 6 | } 7 | 8 | .main--controls--action-menu--item.cobalt, 9 | .main--controls--button.cobalt { 10 | background: #002240; 11 | border: 1px solid #ddd; 12 | } 13 | 14 | .main--controls--action-menu--item.cobalt:hover, 15 | .main--controls--button.cobalt:hover { 16 | background: #1b3954; 17 | } 18 | 19 | .main--controls--button.cobalt:active { 20 | background: #0d2e4a; 21 | } 22 | 23 | .loader-container.cobalt, 24 | .sidebar--output--messages.cobalt { 25 | background: #002240; 26 | } 27 | 28 | .loader.cobalt, 29 | .sidebar--menu--detail--button.solution.cobalt { 30 | color: #08f; 31 | } 32 | 33 | .sidebar--menu--detail--button.resources.cobalt { 34 | color: #ffee80; 35 | } 36 | 37 | .sidebar--menu--sub-header.cobalt { 38 | background: rgba(255,255,255,0.05); 39 | } 40 | 41 | .sidebar--menu--sub-header.cobalt:hover, 42 | .sidebar--menu--detail.cobalt:hover { 43 | background: rgba(255,255,255,0.1); 44 | } 45 | 46 | .sidebar--menu--detail.cobalt.active { 47 | background: rgba(255,255,255,0.03); 48 | } 49 | 50 | .sidebar--menu.cobalt > details { 51 | width: 92%; 52 | } 53 | 54 | .toast.cobalt { 55 | background: #08f; 56 | } 57 | 58 | .sidebar--menu--input.cobalt, 59 | .sidebar--menu--input-icon.cobalt { 60 | color: white; 61 | } 62 | 63 | .sidebar--menu--input.cobalt:focus { 64 | border-bottom: 2px solid #08f; 65 | } 66 | 67 | .modal--confirm--yes.cobalt:hover, 68 | .modal--confirm--no.cobalt:hover { 69 | border-color: #08f; 70 | color: #ffee80; 71 | } 72 | 73 | .modal--confirm--yes.cobalt:active, 74 | .modal--confirm--no.cobalt:active { 75 | border-color: #ffee80; 76 | color: #08f; 77 | } 78 | -------------------------------------------------------------------------------- /src/styles/themes/eclipse.css: -------------------------------------------------------------------------------- 1 | .loader-container.eclipse, 2 | .sidebar.left-pane.eclipse, 3 | .sidebar--menu--detail.eclipse { 4 | color: #7F0055; 5 | background: #f5f5f5; 6 | } 7 | 8 | .sidebar--menu--input.eclipse, 9 | .sidebar--menu--input-icon.eclipse, 10 | .sidebar--menu--detail--button.solution.eclipse { 11 | color: #7F0055; 12 | } 13 | 14 | .loader.eclipse, 15 | .sidebar--menu--detail--button.resources.eclipse { 16 | color: #3F7F5F; 17 | } 18 | 19 | .CodeMirror.cm-s-eclipse div.CodeMirror-cursor { 20 | border-left: 1px solid #7F0055 !important; 21 | } 22 | 23 | .sidebar--menu--sub-header.eclipse:hover, 24 | .sidebar--menu--detail.eclipse:hover { 25 | background: rgba(0,0,0,0.08); 26 | } 27 | 28 | .sidebar--menu--detail.eclipse.active { 29 | background: rgba(0,0,0,0.03); 30 | } 31 | 32 | .sidebar--menu.eclipse > details { 33 | width: 92%; 34 | } 35 | 36 | .toast.eclipse { 37 | background: #7F0055; 38 | } 39 | 40 | .sidebar--menu--input.eclipse:focus { 41 | border-bottom: 2px solid #cc7; 42 | } 43 | 44 | .modal--confirm--yes.eclipse:hover, 45 | .modal--confirm--no.eclipse:hover { 46 | border-color: #7F0055; 47 | color: #cc7; 48 | } 49 | 50 | .modal--confirm--yes.eclipse:active, 51 | .modal--confirm--no.eclipse:active { 52 | border-color: #cc7; 53 | color: #7F0055; 54 | } 55 | -------------------------------------------------------------------------------- /src/styles/themes/icecoder.css: -------------------------------------------------------------------------------- 1 | .loader.icecoder, 2 | .sidebar--menu--detail--button.solution.icecoder { 3 | color: #b9ca4a; 4 | } 5 | 6 | .sidebar--menu--detail--button.resources.icecoder { 7 | color: #6cb5d9; 8 | } 9 | 10 | .loader-container.icecoder { 11 | background: #1d1d1b; 12 | } 13 | 14 | .toast.icecoder { 15 | background: #6cb5d9; 16 | } 17 | 18 | .sidebar--menu--input.icecoder:focus { 19 | border-bottom: 2px solid #b9ca4a; 20 | } 21 | 22 | .modal--confirm--yes.icecoder:hover, 23 | .modal--confirm--no.icecoder:hover { 24 | border-color: #6cb5d9; 25 | color: #b9ca4a; 26 | } 27 | 28 | .modal--confirm--yes.icecoder:active, 29 | .modal--confirm--no.icecoder:active { 30 | border-color: #b9ca4a; 31 | color: #6cb5d9; 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/themes/lesser-dark.css: -------------------------------------------------------------------------------- 1 | .loader.lesser-dark, 2 | .sidebar--menu--detail--button.solution.lesser-dark { 3 | color: #599eff; 4 | } 5 | 6 | .sidebar--menu--detail--button.resources.lesser-dark { 7 | color: #D9BF8C; 8 | } 9 | 10 | .sidebar--menu.lesser-dark > details { 11 | width: 92%; 12 | } 13 | 14 | .loader-container.lesser-dark { 15 | background: #262626; 16 | } 17 | 18 | .toast.lesser-dark { 19 | background: #599eff; 20 | } 21 | 22 | .sidebar--menu--input.lesser-dark:focus { 23 | border-bottom: 2px solid #D9BF8C; 24 | } 25 | 26 | .modal--confirm--yes.lesser-dark:hover, 27 | .modal--confirm--no.lesser-dark:hover { 28 | border-color: #D9BF8C; 29 | color: #599eff; 30 | } 31 | 32 | .modal--confirm--yes.lesser-dark:active, 33 | .modal--confirm--no.lesser-dark:active { 34 | border-color: #599eff; 35 | color: #D9BF8C; 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/themes/material.css: -------------------------------------------------------------------------------- 1 | .loader.material, 2 | .sidebar--menu--detail--button.resources.material { 3 | color: rgba(199, 146, 234, .7); 4 | } 5 | 6 | .sidebar--menu--detail--button.solution.material { 7 | color: #F77669; 8 | } 9 | 10 | .loader-container.material { 11 | background: #263238; 12 | } 13 | 14 | .toast.material { 15 | background: #F77669; 16 | } 17 | 18 | .sidebar--menu--input.material:focus { 19 | border-bottom: 2px solid rgba(199, 146, 234, .7); 20 | } 21 | 22 | .modal--confirm--yes.material:hover, 23 | .modal--confirm--no.material:hover { 24 | border-color: rgba(199, 146, 234, .7); 25 | color: #F77669; 26 | } 27 | 28 | .modal--confirm--yes.material:active, 29 | .modal--confirm--no.material:active { 30 | border-color: #F77669; 31 | color: rgba(199, 146, 234, .7); 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/themes/monokai.css: -------------------------------------------------------------------------------- 1 | .sidebar--menu--detail--button.solution.monokai { 2 | color: #fd971f; 3 | } 4 | 5 | .loader.monokai, 6 | .sidebar--menu--detail--button.resources.monokai { 7 | color: #ae81ff; 8 | } 9 | 10 | .sidebar--menu.monokai > details { 11 | width: 92%; 12 | } 13 | 14 | .loader-container.monokai { 15 | background: #272822; 16 | } 17 | 18 | .toast.monokai { 19 | background: #fd971f; 20 | } 21 | 22 | .sidebar--menu--input.monokai:focus { 23 | border-bottom: 2px solid #ae81ff; 24 | } 25 | 26 | .modal--confirm--yes.monokai:hover, 27 | .modal--confirm--no.monokai:hover { 28 | border-color: #ae81ff; 29 | color: #fd971f; 30 | } 31 | 32 | .modal--confirm--yes.monokai:active, 33 | .modal--confirm--no.monokai:active { 34 | border-color: #fd971f; 35 | color: #ae81ff; 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/themes/neat.css: -------------------------------------------------------------------------------- 1 | .sidebar--menu--detail--button.solution.neat { 2 | color: #a86; 3 | } 4 | 5 | .loader.neat, 6 | .sidebar--menu--detail--button.resources.neat { 7 | color: #3a3; 8 | } 9 | 10 | .CodeMirror.cm-s-neat div.CodeMirror-cursor { 11 | border-left: 1px solid #ef6155 !important; 12 | } 13 | 14 | .sidebar--menu--sub-header.neat { 15 | background: rgba(255,255,255,0.05); 16 | } 17 | 18 | .sidebar--menu--sub-header.neat:hover, 19 | .sidebar--menu--detail.neat:hover { 20 | background: rgba(255,255,255,0.1); 21 | } 22 | 23 | .sidebar--menu--detail.neat.active { 24 | background: rgba(255,255,255,0.03); 25 | } 26 | 27 | .loader-container.neat { 28 | background: #272822; 29 | } 30 | 31 | .toast.neat { 32 | background: #a86; 33 | } 34 | 35 | .sidebar--menu--input.neat:focus { 36 | border-bottom: 2px solid #3a3; 37 | } 38 | 39 | .modal--confirm--yes.neat:hover, 40 | .modal--confirm--no.neat:hover { 41 | border-color: #3a3; 42 | color: #a86; 43 | } 44 | 45 | .modal--confirm--yes.neat:active, 46 | .modal--confirm--no.neat:active { 47 | border-color: #a86; 48 | color: #3a3; 49 | } 50 | -------------------------------------------------------------------------------- /src/styles/themes/panda-syntax.css: -------------------------------------------------------------------------------- 1 | .sidebar--menu--detail--button.solution.panda-syntax { 2 | color: #FF75B5; 3 | } 4 | 5 | .sidebar--menu--detail--button.resources.panda-syntax, 6 | .loader.panda-syntax { 7 | color: #ffb86c; 8 | } 9 | 10 | .loader-container.panda-syntax { 11 | background: #292A2B; 12 | } 13 | 14 | .toast.panda-syntax { 15 | background: #ffb86c; 16 | color: #292A2B; 17 | } 18 | 19 | .sidebar--menu--input.panda-syntax:focus { 20 | border-bottom: 2px solid #FF75B5; 21 | } 22 | 23 | .modal--confirm--yes.panda-syntax:hover, 24 | .modal--confirm--no.panda-syntax:hover { 25 | border-color: #FF75B5; 26 | color: #ffb86c; 27 | } 28 | 29 | .modal--confirm--yes.panda-syntax:active, 30 | .modal--confirm--no.panda-syntax:active { 31 | border-color: #ffb86c; 32 | color: #FF75B5; 33 | } 34 | -------------------------------------------------------------------------------- /src/styles/themes/paraiso-light.css: -------------------------------------------------------------------------------- 1 | .sidebar.left-pane.paraiso-light { 2 | color: #ef6155; 3 | background: #e7e9db; 4 | } 5 | 6 | .sidebar--menu--detail.paraiso-light { 7 | color: #815ba4; 8 | color: #ef6155; 9 | background: #e7e9db; 10 | } 11 | 12 | .CodeMirror.cm-s-paraiso-light div.CodeMirror-cursor { 13 | border-left: 1px solid #ef6155 !important; 14 | } 15 | 16 | .sidebar--output--messages.paraiso-light { 17 | background: #d4d6c6; 18 | color: #368f67; 19 | } 20 | 21 | .sidebar--output--messages.paraiso-light code { 22 | color: #66465e; 23 | } 24 | 25 | .sidebar--output--messages.paraiso-light span.type { 26 | color: #815ba4; 27 | font-weight: bold; 28 | } 29 | 30 | .sidebar--output--messages.paraiso-light p.error { 31 | color: #ef6155; 32 | } 33 | 34 | .main--controls.paraiso-light { 35 | background: #d4d6c6; 36 | } 37 | 38 | .main--controls--action-menu--item.paraiso-light, 39 | .main--controls--button.paraiso-light { 40 | color: #e96ba8; 41 | background: #d4d6c6; 42 | border-left: 3px solid #e7e9db; 43 | border-right: none; 44 | font-weight: bold; 45 | } 46 | 47 | .main--controls--button.paraiso-light:first-of-type { 48 | border-left: none; 49 | } 50 | 51 | .main--controls--button.paraiso-light.run-code, 52 | .main--controls--button.paraiso-light.previous { 53 | border-right: none; 54 | } 55 | 56 | .main--controls--action-menu--item.paraiso-light:hover, 57 | .main--controls--button.paraiso-light:hover { 58 | background: #dfe0d5; 59 | } 60 | 61 | .main--controls--button.paraiso-light:active { 62 | background: rgba(0,0,0,0.03); 63 | } 64 | 65 | .sidebar--menu--sub-header.paraiso-light:hover, 66 | .sidebar--menu--detail.paraiso-light:hover { 67 | background: rgba(0,0,0,0.08); 68 | } 69 | 70 | .sidebar--menu--detail.paraiso-light.active { 71 | background: rgba(0,0,0,0.03); 72 | } 73 | 74 | .sidebar--menu--detail--button.solution.paraiso-light, 75 | .sidebar--menu--input-icon.paraiso-light, 76 | .sidebar--menu--input.paraiso-light { 77 | color: #ef6155; 78 | } 79 | 80 | .sidebar--menu--detail--button.resources.paraiso-light { 81 | color: #48b685; 82 | } 83 | 84 | .sidebar--menu.paraiso-light > details { 85 | width: 96%; 86 | } 87 | 88 | .loader-container.paraiso-light { 89 | background: #e7e9db; 90 | } 91 | 92 | .loader.paraiso-light { 93 | color: #e96ba8; 94 | } 95 | 96 | .toast.paraiso-light { 97 | background: #ef6155; 98 | } 99 | 100 | .sidebar--menu--input.paraiso-light:focus { 101 | border-bottom: 2px solid #06b6ef; 102 | } 103 | 104 | .modal--confirm--yes.paraiso-light:hover, 105 | .modal--confirm--no.paraiso-light:hover { 106 | border-color: #ef6155; 107 | color: #06b6ef; 108 | } 109 | 110 | .modal--confirm--yes.paraiso-light:active, 111 | .modal--confirm--no.paraiso-light:active { 112 | border-color: #06b6ef; 113 | color: #ef6155; 114 | } 115 | -------------------------------------------------------------------------------- /src/styles/themes/tomorrow-night-eighties.css: -------------------------------------------------------------------------------- 1 | .sidebar--menu--detail--button.solution.tomorrow-night-eighties { 2 | color: #f2777a; 3 | } 4 | 5 | .sidebar--menu--detail--button.resources.tomorrow-night-eighties, 6 | .loader.tomorrow-night-eighties { 7 | color: #99cc99; 8 | } 9 | 10 | .sidebar--menu--input.tomorrow-night-eighties:focus { 11 | border-bottom: 2px solid #f2777a; 12 | } 13 | 14 | .loader-container.tomorrow-night-eighties { 15 | color: #000000; 16 | } 17 | 18 | .toast.tomorrow-night-eighties { 19 | background: #f2777a; 20 | } 21 | 22 | .modal--confirm--yes.tomorrow-night-eighties:hover, 23 | .modal--confirm--no.tomorrow-night-eighties:hover { 24 | border-color: #f2777a; 25 | color: #99cc99; 26 | } 27 | 28 | .modal--confirm--yes.tomorrow-night-eighties:active, 29 | .modal--confirm--no.tomorrow-night-eighties:active { 30 | border-color: #99cc99; 31 | color: #f2777a; 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/themes/twilight.css: -------------------------------------------------------------------------------- 1 | .sidebar--menu--detail--button.solution.twilight { 2 | color: #ca7841; 3 | } 4 | 5 | .sidebar--menu--detail--button.resources.twilight, 6 | .loader.twilight { 7 | color: #f9ee98; 8 | } 9 | 10 | .sidebar--menu.twilight > details { 11 | width: 92%; 12 | } 13 | 14 | .loader-container.twilight { 15 | background: #141414; 16 | } 17 | 18 | .toast.twilight { 19 | background: #ca7841; 20 | color: #141414; 21 | } 22 | 23 | .sidebar--menu--input.twilight:focus { 24 | border-bottom: 2px solid #ca7841; 25 | } 26 | 27 | .modal--confirm--yes.twilight:hover, 28 | .modal--confirm--no.twilight:hover { 29 | border-color: #ca7841; 30 | color: #f9ee98; 31 | } 32 | 33 | .modal--confirm--yes.twilight:active, 34 | .modal--confirm--no.twilight:active { 35 | border-color: #f9ee98; 36 | color: #ca7841; 37 | } 38 | -------------------------------------------------------------------------------- /src/types/Actions.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type AddRepl = { type: 'ADD_REPL', id: string } 3 | export type ClearConsole = { type: 'CLEAR_CONSOLE' } 4 | export type CloseModal = { type: 'CLOSE_MODAL' } 5 | export type ConsoleLog = { type: 'CONSOLE_LOG', logs: string } 6 | export type DeleteRepl = { type: 'DELETE_REPL', id: string } 7 | export type DoubleClick = { type: 'DOUBLE_CLICK' } 8 | export type DragHorizontal = { 9 | type: 'DRAG_HORIZONTAL', 10 | leftWidth: string, 11 | rightWidth: string 12 | } 13 | export type DragVertical = { 14 | type: 'DRAG_VERTICAL', 15 | bottomHeight: string, 16 | topHeight: string 17 | } 18 | export type LoadRepl = { type: 'LOAD_SHARED_REPL', code: string } 19 | export type NextChallenge = { type: 'NEXT_CHALLENGE' } 20 | export type NextTheme = { type: 'NEXT_THEME' } 21 | export type OnDrag = ( 22 | el: Object, 23 | pageX: number, 24 | startX: number, 25 | pageY: number, 26 | startY: number, 27 | resize: Object 28 | ) => void 29 | export type OpenAnnouncementModal = { 30 | type: 'OPEN_ANNOUNCEMENT_MODAL', 31 | id: string, 32 | subHeader: string, 33 | messages: string[] 34 | } 35 | export type OpenKeyBindingsModal = { 36 | type: 'OPEN_KEY_BINDINGS_MODAL', 37 | id: string, 38 | messages: string 39 | } 40 | export type OpenConfirmModal = { type: 'OPEN_CONFIRM_MODAL', id: string } 41 | export type OpenResourcesModal = { type: 'OPEN_RESOURCES_MODAL', id: string } 42 | export type OpenThemeModal = { type: 'OPEN_THEME_MODAL', id: string } 43 | export type PrevChallenge = { type: 'PREV_CHALLENGE' } 44 | export type PrevTheme = { type: 'PREV_THEME' } 45 | export type ResetState = { type: 'RESET_STATE' } 46 | export type SelectChallenge = { type: 'SELECT_CHALLENGE', id: string } 47 | export type SelectSolution = { type: 'SELECT_SOLUTION', id: string } 48 | export type ToggleMenu = { 49 | type: 'TOGGLE_MENU', 50 | data: { 51 | name: string, 52 | open: boolean 53 | } 54 | } 55 | export type ToggleSolution = { type: 'TOGGLE_SOLUTION', id: string } 56 | export type UpdateCode = { 57 | type: 'UPDATE_CODE', 58 | code: string 59 | } 60 | 61 | export type Action = 62 | | AddRepl 63 | | ClearConsole 64 | | CloseModal 65 | | ConsoleLog 66 | | DeleteRepl 67 | | DoubleClick 68 | | DragHorizontal 69 | | DragVertical 70 | | LoadRepl 71 | | NextChallenge 72 | | NextTheme 73 | | OpenAnnouncementModal 74 | | OpenKeyBindingsModal 75 | | OpenConfirmModal 76 | | OpenResourcesModal 77 | | OpenThemeModal 78 | | PrevChallenge 79 | | PrevTheme 80 | | ResetState 81 | | SelectChallenge 82 | | SelectSolution 83 | | ToggleMenu 84 | | ToggleSolution 85 | | UpdateCode 86 | -------------------------------------------------------------------------------- /src/types/Reducers.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Action } from './Actions' 3 | import type { Code } from '../assets/codeRef' 4 | 5 | // Editor: 6 | export type CodeStore = Array<{ 7 | id: string, 8 | userCode: string 9 | }> 10 | export type CurrentEditorState = { 11 | +id: string, 12 | +code: string, 13 | +isSolution: boolean 14 | } 15 | export type EditorState = { 16 | +isSharedRepl: boolean, 17 | +current: { 18 | +id: string, 19 | +code: string, 20 | +isSolution: boolean 21 | }, 22 | +codeStore: CodeStore, 23 | +orderKey: string[] 24 | } 25 | export type EditorReducer = (EditorState, Action) => EditorState 26 | 27 | // Editor Utils: 28 | export type MergeCodeStores = (CodeStore, CodeStore) => CodeStore 29 | export type RemoveDupes = (c: CodeStore) => CodeStore 30 | export type AddSuppressTests = ( 31 | cs: CodeStore, 32 | ces: CurrentEditorState, 33 | orderKey: string[] 34 | ) => { 35 | codeStore: CodeStore, 36 | current: CurrentEditorState 37 | } 38 | export type ComposeCodeStore = ( 39 | i: EditorState, 40 | d: EditorState 41 | ) => { 42 | codeStore: CodeStore, 43 | current: CurrentEditorState 44 | } 45 | export type CheckForUpdates = ( 46 | i: EditorState, 47 | d: EditorState, 48 | c: Code 49 | ) => EditorState 50 | 51 | export type RunInitializationUtils = ( 52 | i: EditorState, 53 | s: ?string, 54 | c: Code 55 | ) => EditorState 56 | 57 | // Modal 58 | export type ModalState = { 59 | +messages: string[] | string, 60 | +modalId: string, 61 | +modalType: string, 62 | +renderModal: boolean, 63 | +subHeader?: string, 64 | } 65 | 66 | // Menu 67 | export type MenuState = Array<{ +name: string, +open: boolean }> 68 | 69 | 70 | // Panes 71 | export type PanesState = { 72 | +topHeight: string, 73 | +bottomHeight: string, 74 | +leftWidth: string, 75 | +rightWidth: string, 76 | +transition: string, 77 | +clickState: number 78 | } 79 | 80 | // Themes 81 | export type ThemeState = { 82 | +current: string, 83 | +themes: string[] 84 | } 85 | -------------------------------------------------------------------------------- /src/types/State.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { 3 | EditorState, 4 | MenuState, 5 | ModalState, 6 | PanesState, 7 | ThemeState 8 | } from './Reducers' 9 | 10 | import type { Action } from './Actions' 11 | 12 | export type State = {| 13 | console: (state?: string[], action: Action) => string[], 14 | editor: (state?: EditorState, action: Action) => EditorState, 15 | menu: (state?: MenuState, action: Action) => MenuState, 16 | modal: (state?: ModalState, action: Action) => ModalState, 17 | panes: (state?: PanesState, action: Action) => PanesState, 18 | theme: (state?: ThemeState, action: Action) => ThemeState 19 | |} 20 | -------------------------------------------------------------------------------- /src/utils/createProxyConsole.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { disableProxyConsole, store } from '../index' 3 | import { join, map } from 'lodash' 4 | 5 | import { consoleLog } from '../actions/console' 6 | 7 | const concatLogs = (arr: mixed[]): string => { 8 | return join( 9 | map(arr, msg => { 10 | // NOTE: Do not stringify DLL 11 | if (typeof msg === 'object' 12 | && msg !== null 13 | && msg.hasOwnProperty('prev')) 14 | return msg 15 | else if (typeof msg !== 'string') 16 | return JSON.stringify(msg) 17 | return msg 18 | }), 19 | ' ' 20 | ) 21 | } 22 | 23 | // createProxyConsole 24 | export default () => { 25 | if (!disableProxyConsole) { 26 | const OG_Log = console.log 27 | // $FlowFixMe, ignore 28 | console.log = function(...args) { 29 | const logs = concatLogs([...args]) 30 | store.dispatch(consoleLog(logs)) 31 | OG_Log.apply(console, [...args]) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/editorConfig.js: -------------------------------------------------------------------------------- 1 | import toggleControlComments from './toggleControlComments'; 2 | 3 | export default { 4 | indentUnit: 4, 5 | foldGutter: true, 6 | lineNumbers: true, 7 | matchBrackets: true, 8 | styleActiveLine: true, 9 | autoCloseBrackets: true, 10 | highlightSelectionMatches: true, 11 | mode: "javascript", 12 | keyMap: 'sublime', 13 | gutters: [ 14 | 'CodeMirror-linenumbers', 15 | 'CodeMirror-foldgutter', 16 | 'CodeMirror-lint-markers' 17 | ], 18 | lint: { 19 | // allow ES6 syntax 20 | esversion: 6, 21 | // suppress multi-line ternary warnings 22 | laxbreak: true, 23 | // suppress missing semi-colon warnings 24 | asi: true 25 | }, 26 | extraKeys: { 27 | // prevent default 28 | 'Ctrl-Enter': () => { 29 | return false 30 | }, 31 | // prevent default 32 | 'Cmd-Enter': () => { 33 | return false 34 | }, 35 | 'Ctrl-Space': (cm) => { 36 | cm.showHint() 37 | }, 38 | 'Ctrl-Alt-/': (cm) => { 39 | toggleControlComments(cm, /\/\/\s+SUPPRESS\s+TESTS/i, '// SUPPRESS TESTS') 40 | }, 41 | 'Cmd-Alt-/': (cm) => { 42 | toggleControlComments(cm, /\/\/\s+SUPPRESS\s+TESTS/i, '// SUPPRESS TESTS') 43 | }, 44 | 'Ctrl-Alt-L': (cm) => { 45 | toggleControlComments(cm, /\/\/\s+DISABLE\s+LOOP\s+PROTECT/i, '// DISABLE LOOP PROTECT') 46 | }, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/localStorageKeys.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export const MENU_STATE = 'cs-pg-react-menuState' 3 | export const PANES_STATE = 'cs-pg-react-panesState' 4 | export const THEME_STATE = 'cs-pg-react-themeState' 5 | export const EDITOR_STATE = 'cs-pg-react-editorState' 6 | export const REMOVE_DUPES = 'cs-pg-react-dupes-removed' 7 | export const RENDER_MODAL = 'cs-pg-react-render-only-thrice' 8 | export const IS_SUITE_SUPPRESSED = 'cs-pg-react-tests-suppressed' 9 | export const ALL_TESTS_SUPPRESSED = 'cs-pg-react-suppress-tests-only-once' 10 | export const IS_LOOP_PROTECT_DISABLED = 'cs-pg-react-disable-loop-protect' 11 | export const RESET_ANNOUNCEMENT_COUNT = 'cs-pg-react-reset-announcement-count' 12 | -------------------------------------------------------------------------------- /src/utils/onDrag.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { dragHorizontal, dragVertical } from '../actions/panes' 3 | 4 | import type { OnDrag } from '../types/Actions' 5 | import { store } from '../index' 6 | 7 | export const onDragHorizontal: OnDrag = (el, pageX, startX, pageY, startY, resize) => { 8 | resize.skipX = true 9 | const LEFT_THRESHOLD = 30, RIGHT_THRESHOLD = 70 10 | if (pageX < window.innerWidth * LEFT_THRESHOLD / 100) { 11 | pageX = window.innerWidth * LEFT_THRESHOLD / 100 12 | resize.pageX = pageX 13 | } 14 | if (pageX > window.innerWidth * RIGHT_THRESHOLD / 100) { 15 | pageX = window.innerWidth * RIGHT_THRESHOLD / 100 16 | resize.pageX = pageX 17 | } 18 | 19 | let cur = pageX / window.innerWidth * 100 20 | if (cur < 0) { 21 | cur = 0 22 | } 23 | if (cur > window.innerWidth) { 24 | cur = window.innerWidth 25 | } 26 | 27 | const right = 100 - cur - .5 28 | store.dispatch(dragHorizontal(cur, right)) 29 | } 30 | 31 | export const onDragVertical: OnDrag = (el, pageX, startX, pageY, startY, resize) => { 32 | resize.skipY = true 33 | const TOP_THRESHOLD = 0, BOTTOM_THRESHOLD = 100 34 | if (pageY < window.innerHeight * TOP_THRESHOLD / 100) { 35 | pageY = window.innerHeight * TOP_THRESHOLD / 100 36 | resize.pageY = pageY 37 | } 38 | if (pageY > window.innerHeight * BOTTOM_THRESHOLD / 100) { 39 | pageY = window.innerHeight * BOTTOM_THRESHOLD / 100 40 | resize.pageY = pageY 41 | } 42 | 43 | let cur = pageY / window.innerHeight * 100 44 | if (cur < 0) { 45 | cur = 0 46 | } 47 | if (cur > window.innerHeight) { 48 | cur = window.innerHeight 49 | } 50 | 51 | const bottom = 100 - cur - 1 52 | store.dispatch(dragVertical(cur, bottom)) 53 | } 54 | -------------------------------------------------------------------------------- /src/utils/regexp.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // regexp constructors 3 | export const RESET_STATE = new RegExp('resetState\\(\\)') 4 | export const SUPPRESS_TESTS = new RegExp('\\/\\/\\s\\s?SUPPRESS\\s\\s?TESTS', 'i') 5 | export const DO_NOT_SAVE = new RegExp('\\/\\/\\s\\s?DO\\s\\s?NOT\\s\\s?SAVE', 'i') 6 | export const DISABLE_LOOP_PROTECT = new RegExp('\\/\\/\\s\\s?DISABLE\\s\\s?LOOP\\s\\s?PROTECT', 'i') 7 | export const ERROR_TYPES = new RegExp('tests failed|WARNING:|Fail:|AssertionError|InternalError|RangeError|ReferenceError|EvalError|SyntaxError|TypeError|URIError|Error:') 8 | -------------------------------------------------------------------------------- /src/utils/test/app/create-jest-test.js: -------------------------------------------------------------------------------- 1 | import { concatTests, logResults } from './jest-test-utils' 2 | 3 | import { SOLUTIONS } from '../../../assets/codeRef' 4 | import TESTS from '../../../assets/testRef' 5 | 6 | export default (ID) => { 7 | test(ID, () => { 8 | // eslint-disable-next-line 9 | const { passed, results } = eval( 10 | concatTests( 11 | SOLUTIONS.get(ID), 12 | TESTS[ID].tail ? TESTS[ID].tail : '', 13 | JSON.stringify(TESTS[ID].tests) 14 | ) 15 | ) 16 | logResults(passed, results, ID) 17 | expect(passed).toBe(true) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/test/app/jest-test-scripts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-eval */ 2 | 3 | // stringify functions, concat in correct order and 4 | // pass to eval for crappy way to test solution code 5 | const suppressConsole = () => ({ 6 | log: (arg) => { 7 | if (typeof arg === 'string' && 8 | (arg.includes('Pass:') || !arg.includes('Fail:')) 9 | ) { 10 | return arg 11 | } 12 | return null 13 | } 14 | }) 15 | 16 | function executeTests(tests, hook = {}) { 17 | let passed = true 18 | const results = [] 19 | /* eslint-disable no-unused-vars */ 20 | const isTestDisabled = require('../common/is-test-disabled') 21 | const { invoke, forEach } = require('lodash') 22 | const assert = require('assert') 23 | /* eslint-enable no-unused-vars */ 24 | if (tests) { 25 | invoke(hook, 'beforeAll') 26 | forEach(tests, test => { 27 | try { 28 | invoke(hook, 'beforeEach') 29 | if (test.method) { 30 | // assert w/ method 31 | assert[test.method]( 32 | eval(test.expression), 33 | test.expected, 34 | test.message 35 | ) 36 | } else { 37 | // assert w/o method 38 | assert(eval(test.expression), test.message) 39 | } 40 | invoke(hook, 'afterEach') 41 | results.push('Pass: ' + test.message) 42 | } catch (e) { 43 | results.push('Fail: ' + test.message) 44 | passed = false 45 | } 46 | }) 47 | } 48 | invoke(hook, 'afterAll') 49 | return { passed, results } 50 | } 51 | 52 | export const __suppressConsole__ = suppressConsole.toString() 53 | export const __executeTests__ = executeTests.toString() 54 | -------------------------------------------------------------------------------- /src/utils/test/app/jest-test-utils.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | 3 | import { 4 | __executeTests__, 5 | __suppressConsole__ 6 | } from './jest-test-scripts' 7 | 8 | export const concatTests = ( 9 | solution, 10 | tail, 11 | tests 12 | ) => { 13 | return ` 14 | // suppress logs during tests 15 | const console = (${__suppressConsole__})(); 16 | // execute tests 17 | (() => { 18 | ${solution} 19 | ${tail} 20 | const tests = ${tests}; 21 | ${__executeTests__} 22 | return typeof testHooks !== 'undefined' 23 | ? executeTests(tests, testHooks) 24 | : executeTests(tests); 25 | })(); 26 | ` 27 | } 28 | 29 | export const logResults = (passed, results, id) => { 30 | if (!passed) { 31 | // LOGS ONLY FAILING SUITES 32 | console.log(chalk.keyword('salmon').underline(id + ':')) 33 | results.forEach(t => t[0] === 'F' 34 | ? console.log(chalk.red(t)) 35 | : console.log(t) 36 | ) 37 | } else { 38 | // UNCOMMENT TO SEE ALL RESULTS 39 | // console.log(chalk.keyword('salmon').underline(id + ':')); 40 | // results.forEach(t => console.log(t)) 41 | } 42 | } 43 | 44 | export const suppressConsole = ` 45 | let oldConsoleLog = console.log 46 | console.log = () => {} 47 | ` 48 | export const enableConsole = ` 49 | console.log = oldConsoleLog 50 | ` 51 | -------------------------------------------------------------------------------- /src/utils/test/challenge/eval-code-run-tests.js: -------------------------------------------------------------------------------- 1 | import { SUPPRESS_TESTS, DISABLE_LOOP_PROTECT } from '../../regexp' 2 | import executeTests from './execute-tests' 3 | import loopProtect from './loop-protect' 4 | import TESTS from '../../../assets/testRef' 5 | /* eslint-disable no-eval */ 6 | 7 | export default (code, id) => { 8 | 9 | // import funcs used in executeTests so in 10 | // scope during eval, see ./execute-tests.js 11 | 12 | /* eslint-disable no-unused-vars */ 13 | const assert = require('assert') 14 | const { forEach, invoke } = require('lodash') 15 | const { beginTests, logTestReport } = require('./testReport') 16 | const isTestDisabled = require('../common/is-test-disabled') 17 | /* eslint-enable no-unused-vars */ 18 | 19 | let prepend = 'const tests = ', tail = '', tests = '' 20 | 21 | // if suppressTests is true or tests do not exist 22 | // only eval code, otherwise eval & execute tests 23 | if (!SUPPRESS_TESTS.test(code) && TESTS[id]) { 24 | tests = prepend + JSON.stringify(TESTS[id].tests, null, 2) 25 | if (TESTS[id].tail) tail += TESTS[id].tail 26 | } 27 | 28 | // apply loop-protect if not disabled by user 29 | if (!DISABLE_LOOP_PROTECT.test(code)) 30 | code = loopProtect(code) + '\n\n' 31 | // add line break to counteract 32 | // whitespace trim by loopProtect 33 | 34 | try { 35 | eval( 36 | code + 37 | tail + 38 | tests + 39 | executeTests 40 | ) 41 | } catch (e) { 42 | console.log(e.toString()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/test/challenge/execute-tests.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-eval */ 2 | /* eslint-disable no-undef */ 3 | function executeTests(tests, hook = {}) { 4 | if (tests) { 5 | console.log(beginTests) 6 | // init report vars 7 | let numPassed = 0, numDisabled = 0 8 | // run beforeAll hook 9 | invoke(hook, 'beforeAll') 10 | // iterate tests array 11 | forEach(tests, (test, i) => { 12 | try { 13 | // run beforeEach hook 14 | invoke(hook, 'beforeEach') 15 | // eval to prevent further execution if disabled 16 | if (eval(test.expression) === 'DISABLED') { 17 | numDisabled++ 18 | throw new Error('DISABLED') 19 | } else { 20 | // run beforeEach again since test 21 | // expression has already been evaled 22 | invoke(hook, 'beforeEach') 23 | // test enabled 24 | if (test.method) { 25 | // assert w/ method 26 | assert[test.method]( 27 | eval(test.expression), 28 | test.expected, 29 | test.message 30 | ) 31 | } else { 32 | // assert w/ no method 33 | assert(eval(test.expression), test.message) 34 | } 35 | // run afterEach hook 36 | invoke(hook, 'afterEach') 37 | // log passing test message 38 | console.log('Pass: ' + test.message) 39 | numPassed++ 40 | } 41 | } catch (e) { 42 | // run afterEach hook when test does not pass 43 | invoke(hook, 'afterEach') 44 | // is test disabled? 45 | if (e.message === 'DISABLED') { 46 | // log disabled / greyed out test message 47 | console.log('' + e.message + ': ' + test.message + '') 48 | } 49 | // else if ( // ONLY FOR DEV TO DEBUG TESTS: 50 | // process.env.NODE_ENV !== 'production' && 51 | // e.message !== test.message 52 | // ) { 53 | // console.log('Fail: ' + e.message) 54 | // } 55 | else { 56 | // log just failure message 57 | console.log('Fail: ' + test.message) 58 | } 59 | } 60 | }) 61 | // run afterAll hook 62 | invoke(hook, 'afterAll') 63 | // report results 64 | logTestReport(numPassed, numDisabled, tests) 65 | } 66 | } 67 | 68 | export default ` 69 | ${executeTests} 70 | typeof testHooks !== 'undefined' 71 | ? executeTests(tests, testHooks) 72 | : executeTests(tests) 73 | ` 74 | -------------------------------------------------------------------------------- /src/utils/test/challenge/loop-protect.js: -------------------------------------------------------------------------------- 1 | const Babel = require('babel-standalone') 2 | const protect = require('loop-protect') 3 | 4 | function error(line, char) { 5 | throw new RangeError( 6 | `Potential infinite loop, timed out after 200ms. 7 | at eval:${line}:${char} 8 | at eval 9 | 10 | NOTE: You can disable this feature with // DISABLE LOOP PROTECT` 11 | ) 12 | } 13 | 14 | export default (code) => { 15 | const timeout = 200 16 | Babel.registerPlugin('loopProtection', protect(timeout, error)) 17 | const transform = source => Babel.transform(source, { 18 | plugins: ['loopProtection'], 19 | }).code 20 | return transform(code) 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/test/challenge/testReport.js: -------------------------------------------------------------------------------- 1 | const logTestReport = (numPassed, numDisabled, tests) => { 2 | console.log('\nREPORT:') 3 | console.log('‾‾‾‾‾‾‾') 4 | console.log(`- ${numPassed} out of ${tests.length} tests passed`) 5 | console.log(`- ${tests.length - numPassed} out of ${tests.length} tests failed`) 6 | console.log(`- ${numDisabled} test${numDisabled === 1 ? '' : 's'} disabled (define method to enable)`) 7 | console.log('\n/***** TESTS END *****/\n') 8 | } 9 | 10 | const beginTests = '\n/***** TESTS BEGIN *****/\n' 11 | 12 | module.exports = { logTestReport, beginTests } 13 | -------------------------------------------------------------------------------- /src/utils/test/common/is-test-disabled.js: -------------------------------------------------------------------------------- 1 | // disable non-mandatory tests by default 2 | // tests are enable when user defines method in question 3 | module.exports = function(dataStructure, method) { 4 | if (typeof new dataStructure()[method] === 'undefined') { 5 | return true 6 | } 7 | 8 | return false 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/toggleControlComments.js: -------------------------------------------------------------------------------- 1 | import { store } from '../index'; 2 | import { IS_LOOP_PROTECT_DISABLED, IS_SUITE_SUPPRESSED } from './localStorageKeys'; 3 | 4 | export default function(cm, regexp, replaceText) { 5 | 6 | const KEY = replaceText === '// DISABLE LOOP PROTECT' 7 | ? IS_LOOP_PROTECT_DISABLED 8 | : IS_SUITE_SUPPRESSED 9 | 10 | let isDisabled = JSON.parse( 11 | localStorage.getItem(KEY) 12 | ) 13 | 14 | // isTestsSuppressed map is initialized, init loopProtect here 15 | if (!isDisabled && replaceText === '// DISABLE LOOP PROTECT') { 16 | isDisabled = {} 17 | } 18 | 19 | // get id of current challenge to create 20 | // lookup table of toggle state per challenge 21 | let id = store.getState().editor.current.id 22 | let finished = false 23 | 24 | // first search for match, if match exists, remove, 25 | // set disabled to true, and update local storage map 26 | cm.eachLine(line => { 27 | if (line && line.text.match(regexp)) { 28 | cm.setCursor(line.lineNo()) 29 | cm.execCommand('deleteLine') 30 | cm.execCommand('goLineEnd') 31 | isDisabled[id] = true 32 | finished = true 33 | } 34 | }) 35 | 36 | // no match was found, add control comment 37 | // at cursor position and add new line 38 | if (!finished) { 39 | const { 40 | line 41 | } = cm.getCursor() 42 | if (!/^\s*$/.test(cm.getLine(line))) { 43 | cm.execCommand('goLineEnd') 44 | cm.execCommand('newlineAndIndent') 45 | } 46 | cm.replaceSelection(replaceText) 47 | isDisabled[id] = true 48 | } 49 | 50 | // commit updated map to local storage 51 | localStorage.setItem( 52 | KEY, 53 | JSON.stringify(isDisabled) 54 | ) 55 | } 56 | --------------------------------------------------------------------------------