├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONCEPTS.md ├── README.md ├── bin └── index.js ├── dist ├── build.js └── index.html ├── docs ├── index.html └── psychic-min.css ├── index.js ├── package.json ├── screenshots ├── main.png └── notebook.png ├── src ├── app.js ├── components │ ├── block.js │ ├── chart │ │ ├── axis.js │ │ ├── curve.js │ │ ├── index.js │ │ ├── line.css │ │ ├── line.js │ │ ├── pie.css │ │ ├── pie.js │ │ ├── point.js │ │ ├── points.js │ │ ├── slice.js │ │ └── tooltip.js │ ├── layout.js │ ├── result.js │ └── table.js ├── main.js ├── notebook.js ├── router.js └── style.css └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | docs 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:react/recommended" 5 | ], 6 | "env": { 7 | "es6": true, 8 | "node": true, 9 | "browser": true 10 | }, 11 | "parserOptions": { 12 | "ecmaVersion": 6, 13 | "sourceType": "module", 14 | "ecmaFeatures": { 15 | "jsx": true 16 | } 17 | }, 18 | "plugins": [ 19 | "react" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "6" 5 | 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.0.0 (05/17/2017) 2 | 3 | - complete rewrite of the application 4 | -------------------------------------------------------------------------------- /CONCEPTS.md: -------------------------------------------------------------------------------- 1 | # Concepts 2 | 3 | > These are some concepts to what would be good addition to node-notebook 4 | 5 | - Be able to save code snippets to accounts? 6 | - just save reference to the notebook key to the accounts for now 7 | - be able to have notebooks referenced to user account have named hashes 8 | - ex: /{username}/{hash} 9 | - Running asynchonous code 10 | - running a server in a notebook, something that requires a forever loop or the spawn of a new process 11 | - Being able to transform data 12 | - arrays of objects to a table 13 | - arrays of arrays to a graph 14 | - location data to be displayed on a map 15 | - being able to view canvas or buffer data 16 | - Be able to give the notebook a code snippet and ask it to seed the data? 17 | - Be able to give the notebook a code snippet and ask it to run regression over the code paths to optimize it and point out where optimizations can be done 18 | - Be able to have two or more code snippets and compare the output between the three over time as data is being added or removed to see how well they perform 19 | - Be able to run code in specific vms? (might need to dehydrate heap and rehydrate onto different vm?) 20 | - Use docker as host for the node instance and call it when needed 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-notebook 2 | 3 | [![Npm Version](https://img.shields.io/npm/v/node-notebook.svg)](https://www.npmjs.com/package/node-notebook) 4 | [![Build Status](https://travis-ci.org/gabrielcsapo/node-notebook.svg?branch=master)](https://travis-ci.org/gabrielcsapo/node-notebook) 5 | [![Dependency Status](https://starbuck.gabrielcsapo.com/badge/github/gabrielcsapo/node-notebook/status.svg)](https://starbuck.gabrielcsapo.com/github/gabrielcsapo/node-notebook) 6 | [![devDependency Status](https://starbuck.gabrielcsapo.com/badge/github/gabrielcsapo/node-notebook/dev-status.svg)](https://starbuck.gabrielcsapo.com/github/gabrielcsapo/node-notebook#info=devDependencies) 7 | ![npm license](https://img.shields.io/npm/l/node-notebook.svg) 8 | [![npm](https://img.shields.io/npm/dt/node-notebook.svg)]() 9 | [![npm](https://img.shields.io/npm/dm/node-notebook.svg)]() 10 | 11 | > A notebook service that runs Javascript through the node vm 12 | 13 | # [Concepts](CONCEPTS.md) 14 | 15 | > what are some ideas for node-notebook? 16 | 17 | # Screenshots 18 | 19 | ![main](./screenshots/main.png) 20 | 21 | ![notebook](./screenshots/notebook.png) 22 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | 5 | program 6 | .version(require('../package.json').version) 7 | .option('-d, --db [db]', 'Set the db connection', 'mongodb://localhost/node-notebook') 8 | .parse(process.argv); 9 | 10 | process.env.MONGO_URL = process.env.MONGO_URL || program.db; 11 | 12 | require('../index'); 13 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | node-notebook 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | node-notebook 5 | 6 | 14 | 15 | 16 | 17 | 23 |
24 |

node-notebook

25 | A standalone node repl! ⌨️ 26 |
27 | Try it Now! 28 |
29 |
30 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /docs/psychic-min.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v6.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}hr{display:block;box-sizing:content-box;text-align:center;border:0;height:0;border-top:1px solid #cfcfc4;border-bottom:1px solid rgba(255,255,255,.3)}hr.ellipsis{border-top:0;border-bottom:0}hr.ellipsis:before{font-weight:400;font-style:italic;font-size:28px;letter-spacing:.6em;font-size:13px;content:'...';display:inline-block;margin-left:.6em;color:#000;position:relative}pre{display:block;padding:10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;white-space:normal;background-color:#f5f5f5;border-radius:5px;border-left:.3rem solid transparent}blockquote{padding:10px 20px;font-size:17.5px;border-left:5px solid #eee}code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}a{color:#0079ad;text-decoration:none;font-weight:300}small{color:inherit;font-size:75%;font-weight:400}.alert{padding:10px 0 10px 10px;border-radius:5px;border:1px solid #cfcfc4;position:relative}.alert>.alert-close{font-size:25px;line-height:15px;position:absolute;right:10px;top:10px}.alert.alert-white{background-color:#fff;border:1px solid #e6e6e6}.alert.alert-white *{color:#000}.alert.alert-black{background-color:#000;border:1px solid #000}.alert.alert-black *{color:#fff}.alert.alert-default{background-color:#cfcfc4;border:1px solid #bdbdae}.alert.alert-default *{color:#fff}.alert.alert-primary{background-color:#779ecb;border:1px solid #608dc2}.alert.alert-primary *{color:#fff}.alert.alert-success{background-color:#7d7;border:1px solid #5cd65c}.alert.alert-success *{color:#fff}.alert.alert-info{background-color:#9bddff;border:1px solid #72cfff}.alert.alert-info *{color:#fff}.alert.alert-warning{background-color:#ffb347;border:1px solid #ffa626}.alert.alert-warning *{color:#fff}.alert.alert-danger{background-color:#ff6961;border:1px solid #ff483e}.alert.alert-danger *{color:#fff}.badge{display:table-cell;padding:8px 8px 8px 8px;border-radius:100px;border:1px solid #cfcfc4;text-align:center;vertical-align:middle}.badge.badge-white{background-color:#fff;color:#000}.badge.border-white{color:#000}.badge.badge-black{background-color:#000;color:#fff}.badge.border-black{color:#000}.badge.badge-default{background-color:#cfcfc4;color:#fff}.badge.border-default{color:#cfcfc4}.badge.badge-primary{background-color:#779ecb;color:#fff}.badge.border-primary{color:#779ecb}.badge.badge-success{background-color:#7d7;color:#fff}.badge.border-success{color:#7d7}.badge.badge-info{background-color:#9bddff;color:#fff}.badge.border-info{color:#9bddff}.badge.badge-warning{background-color:#ffb347;color:#fff}.badge.border-warning{color:#ffb347}.badge.badge-danger{background-color:#ff6961;color:#fff}.badge.border-danger{color:#ff6961}label{display:inline-block;margin-bottom:.5rem}input,select,textarea{display:block;padding:.375rem 1% .375rem 1%;line-height:1.5}select{background:url("data:image/svg+xml;utf8,");background-color:#fff;border:1px solid #cfcfc4;background-repeat:no-repeat;background-position:right 10px top 5px;background-size:16px 16px;padding:5px 30px 5px 15px;width:auto;text-align:center;border-radius:5px;appearance:none;-webkit-appearance:none;outline:0}select:active,select:focus{outline:0}select.input-white{border-color:#fff}select.input-black{border-color:#a6a6a6}select.input-default{border-color:#eeeeea}select.input-primary{border-color:#cfdded}select.input-success{border-color:#cff3cf}select.input-info{border-color:#dcf3ff}select.input-warning{border-color:#ffe4bf}select.input-danger{border-color:#ffcbc8}input,textarea{width:98%;margin:0;padding:.375rem 1% .375rem 1%;background-color:#fff;background-image:none;border:1px solid #cfcfc4;border-radius:.25rem}input:focus,textarea:focus{border-color:#779ecb;outline:0}input.input-white,textarea.input-white{border-color:#fff}input.input-white:focus,textarea.input-white:focus{border-color:#bfbfbf}input.input-black,textarea.input-black{border-color:#a6a6a6}input.input-black:focus,textarea.input-black:focus{border-color:#000}input.input-default,textarea.input-default{border-color:#eeeeea}input.input-default:focus,textarea.input-default:focus{border-color:#a2a28c}input.input-primary,textarea.input-primary{border-color:#cfdded}input.input-primary:focus,textarea.input-primary:focus{border-color:#4375af}input.input-success,textarea.input-success{border-color:#cff3cf}input.input-success:focus,textarea.input-success:focus{border-color:#3c3}input.input-info,textarea.input-info{border-color:#dcf3ff}input.input-info:focus,textarea.input-info:focus{border-color:#35baff}input.input-warning,textarea.input-warning{border-color:#ffe4bf}input.input-warning:focus,textarea.input-warning:focus{border-color:#f49000}input.input-danger,textarea.input-danger{border-color:#ffcbc8}input.input-danger:focus,textarea.input-danger:focus{border-color:#ff1509}.btn-group{margin:10px;display:inline-block}.btn-group>.btn{margin:-3px;border-radius:0}.btn-group>.btn:first-child{border-radius:5px 0 0 5px}.btn-group>.btn:last-child{border-radius:0 5px 5px 0}.btn{padding:12px 18px;margin:10px;cursor:pointer;display:inline-block;text-align:center;background-color:#fff;border-radius:5px;border:1px solid #cfcfc4;color:#655d5d}.btn:hover{border-color:#d6d6cd}.btn:active{opacity:.5}.btn:focus{outline:0}.btn.btn-block{width:100%}.btn.border-white{color:#fff}.btn.border-white:active,.btn.border-white:hover{border-color:#fff}.btn.border-white:active{border-color:#d9d9d9;opacity:.5}.btn.btn-white{color:#000;border:1px solid #d9d9d9;background-color:#fff}.btn.btn-white:active,.btn.btn-white:hover{border-color:#d9d9d9}.btn.btn-white:hover{background-color:#fff}.btn.btn-white:active{background-color:#d9d9d9;opacity:.5}.btn.border-black{color:#000}.btn.border-black:active,.btn.border-black:hover{border-color:#262626}.btn.border-black:active{border-color:#000;opacity:.5}.btn.btn-black{color:#fff;border:1px solid #000;background-color:#000}.btn.btn-black:active,.btn.btn-black:hover{border-color:#000}.btn.btn-black:hover{background-color:#262626}.btn.btn-black:active{background-color:#000;opacity:.5}.btn.border-default{color:#cfcfc4}.btn.border-default:active,.btn.border-default:hover{border-color:#d6d6cd}.btn.border-default:active{border-color:#b4b4a3;opacity:.5}.btn.btn-default{color:#fff;border:1px solid #b4b4a3;background-color:#cfcfc4}.btn.btn-default:active,.btn.btn-default:hover{border-color:#b4b4a3}.btn.btn-default:hover{background-color:#d6d6cd}.btn.btn-default:active{background-color:#b4b4a3;opacity:.5}.btn.border-primary{color:#779ecb}.btn.border-primary:active,.btn.border-primary:hover{border-color:#8badd3}.btn.border-primary:active{border-color:#5485be;opacity:.5}.btn.btn-primary{color:#fff;border:1px solid #5485be;background-color:#779ecb}.btn.btn-primary:active,.btn.btn-primary:hover{border-color:#5485be}.btn.btn-primary:hover{background-color:#8badd3}.btn.btn-primary:active{background-color:#5485be;opacity:.5}.btn.border-success{color:#7d7}.btn.border-success:active,.btn.border-success:hover{border-color:#8be28b}.btn.border-success:active{border-color:#4ed34e;opacity:.5}.btn.btn-success{color:#fff;border:1px solid #4ed34e;background-color:#7d7}.btn.btn-success:active,.btn.btn-success:hover{border-color:#4ed34e}.btn.btn-success:hover{background-color:#8be28b}.btn.btn-success:active{background-color:#4ed34e;opacity:.5}.btn.border-info{color:#9bddff}.btn.border-info:active,.btn.border-info:hover{border-color:#aae2ff}.btn.border-info:active{border-color:#5dc8ff;opacity:.5}.btn.btn-info{color:#fff;border:1px solid #5dc8ff;background-color:#9bddff}.btn.btn-info:active,.btn.btn-info:hover{border-color:#5dc8ff}.btn.btn-info:hover{background-color:#aae2ff}.btn.btn-info:active{background-color:#5dc8ff;opacity:.5}.btn.border-warning{color:#ffb347}.btn.border-warning:active,.btn.border-warning:hover{border-color:#ffbe63}.btn.border-warning:active{border-color:#ff9f16;opacity:.5}.btn.btn-warning{color:#fff;border:1px solid #ff9f16;background-color:#ffb347}.btn.btn-warning:active,.btn.btn-warning:hover{border-color:#ff9f16}.btn.btn-warning:hover{background-color:#ffbe63}.btn.btn-warning:active{background-color:#ff9f16;opacity:.5}.btn.border-danger{color:#ff6961}.btn.border-danger:active,.btn.border-danger:hover{border-color:#ff7f79}.btn.border-danger:active{border-color:#ff372c;opacity:.5}.btn.btn-danger{color:#fff;border:1px solid #ff372c;background-color:#ff6961}.btn.btn-danger:active,.btn.btn-danger:hover{border-color:#ff372c}.btn.btn-danger:hover{background-color:#ff7f79}.btn.btn-danger:active{background-color:#ff372c;opacity:.5}.grid{width:100%}.grid:after{clear:both;visibility:hidden;display:block;font-size:0;content:' ';height:0}.grid>*>*{word-wrap:break-word}.grid>div{float:left;box-sizing:border-box;min-height:1px}.grid>.col-0-12{display:none}.grid>.col-1-12{width:8.333333333333332%;margin-left:0;margin-right:0}.grid>.col-2-12{width:16.666666666666664%;margin-left:0;margin-right:0}.grid>.col-3-12{width:25%;margin-left:0;margin-right:0}.grid>.col-4-12{width:33.33333333333333%;margin-left:0;margin-right:0}.grid>.col-5-12{width:41.66666666666667%;margin-left:0;margin-right:0}.grid>.col-6-12{width:50%;margin-left:0;margin-right:0}.grid>.col-7-12{width:58.333333333333336%;margin-left:0;margin-right:0}.grid>.col-8-12{width:66.66666666666666%;margin-left:0;margin-right:0}.grid>.col-9-12{width:75%;margin-left:0;margin-right:0}.grid>.col-10-12{width:83.33333333333334%;margin-left:0;margin-right:0}.grid>.col-11-12{width:91.66666666666666%;margin-left:0;margin-right:0}.grid>.col-12-12{width:100%;margin-left:0;margin-right:0}@media screen and (min-width:16em){.grid>.col-xs-0-12{display:none}.grid>.col-xs-1-12{width:8.3333%;margin-left:0;margin-right:0}.grid>.col-xs-2-12{width:16.6666%;margin-left:0;margin-right:0}.grid>.col-xs-3-12{width:25%;margin-left:0;margin-right:0}.grid>.col-xs-4-12{width:33.3333%;margin-left:0;margin-right:0}.grid>.col-xs-5-12{width:41.6666%;margin-left:0;margin-right:0}.grid>.col-xs-6-12{width:50%;margin-left:0;margin-right:0}.grid>.col-xs-7-12{width:58.3333%;margin-left:0;margin-right:0}.grid>.col-xs-8-12{width:66.6666%;margin-left:0;margin-right:0}.grid>.col-xs-9-12{width:75%;margin-left:0;margin-right:0}.grid>.col-xs-10-12{width:83.3333%;margin-left:0;margin-right:0}.grid>.col-xs-11-12{width:91.6666%;margin-left:0;margin-right:0}.grid>.col-xs-12-12{width:100%;margin-left:0;margin-right:0}}@media screen and (min-width:32em){.grid>.col-sm-0-12{display:none}.grid>.col-sm-1-12{width:8.3333%;margin-left:0;margin-right:0}.grid>.col-sm-2-12{width:16.6666%;margin-left:0;margin-right:0}.grid>.col-sm-3-12{width:25%;margin-left:0;margin-right:0}.grid>.col-sm-4-12{width:33.3333%;margin-left:0;margin-right:0}.grid>.col-sm-5-12{width:41.6666%;margin-left:0;margin-right:0}.grid>.col-sm-6-12{width:50%;margin-left:0;margin-right:0}.grid>.col-sm-7-12{width:58.3333%;margin-left:0;margin-right:0}.grid>.col-sm-8-12{width:66.6666%;margin-left:0;margin-right:0}.grid>.col-sm-9-12{width:75%;margin-left:0;margin-right:0}.grid>.col-sm-10-12{width:83.3333%;margin-left:0;margin-right:0}.grid>.col-sm-11-12{width:91.6666%;margin-left:0;margin-right:0}.grid>.col-sm-12-12{width:100%;margin-left:0;margin-right:0}}@media screen and (min-width:48em){.grid>.col-md-0-12{display:none}.grid>.col-md-1-12{width:8.3333%;margin-left:0;margin-right:0}.grid>.col-md-2-12{width:16.6666%;margin-left:0;margin-right:0}.grid>.col-md-3-12{width:25%;margin-left:0;margin-right:0}.grid>.col-md-4-12{width:33.3333%;margin-left:0;margin-right:0}.grid>.col-md-5-12{width:41.6666%;margin-left:0;margin-right:0}.grid>.col-md-6-12{width:50%;margin-left:0;margin-right:0}.grid>.col-md-7-12{width:58.3333%;margin-left:0;margin-right:0}.grid>.col-md-8-12{width:66.6666%;margin-left:0;margin-right:0}.grid>.col-md-9-12{width:75%;margin-left:0;margin-right:0}.grid>.col-md-10-12{width:83.3333%;margin-left:0;margin-right:0}.grid>.col-md-11-12{width:91.6666%;margin-left:0;margin-right:0}.grid>.col-md-12-12{width:100%;margin-left:0;margin-right:0}}@media screen and (min-width:64em){.grid>.col-lg-0-12{display:none}.grid>.col-lg-1-12{width:8.3333%;margin-left:0;margin-right:0}.grid>.col-lg-2-12{width:16.6666%;margin-left:0;margin-right:0}.grid>.col-lg-3-12{width:25%;margin-left:0;margin-right:0}.grid>.col-lg-4-12{width:33.3333%;margin-left:0;margin-right:0}.grid>.col-lg-5-12{width:41.6666%;margin-left:0;margin-right:0}.grid>.col-lg-6-12{width:50%;margin-left:0;margin-right:0}.grid>.col-lg-7-12{width:58.3333%;margin-left:0;margin-right:0}.grid>.col-lg-8-12{width:66.6666%;margin-left:0;margin-right:0}.grid>.col-lg-9-12{width:75%;margin-left:0;margin-right:0}.grid>.col-lg-10-12{width:83.3333%;margin-left:0;margin-right:0}.grid>.col-lg-11-12{width:91.6666%;margin-left:0;margin-right:0}.grid>.col-lg-12-12{width:100%;margin-left:0;margin-right:0}}@media screen and (min-width:80em){.grid>.col-xl-0-12{display:none}.grid>.col-xl-1-12{width:8.3333%;margin-left:0;margin-right:0}.grid>.col-xl-2-12{width:16.6666%;margin-left:0;margin-right:0}.grid>.col-xl-3-12{width:25%;margin-left:0;margin-right:0}.grid>.col-xl-4-12{width:33.3333%;margin-left:0;margin-right:0}.grid>.col-xl-5-12{width:41.6666%;margin-left:0;margin-right:0}.grid>.col-xl-6-12{width:50%;margin-left:0;margin-right:0}.grid>.col-xl-7-12{width:58.3333%;margin-left:0;margin-right:0}.grid>.col-xl-8-12{width:66.6666%;margin-left:0;margin-right:0}.grid>.col-xl-9-12{width:75%;margin-left:0;margin-right:0}.grid>.col-xl-10-12{width:83.3333%;margin-left:0;margin-right:0}.grid>.col-xl-11-12{width:91.6666%;margin-left:0;margin-right:0}.grid>.col-xl-12-12{width:100%;margin-left:0;margin-right:0}}body{margin:0}.text-white,.text-white>*{color:#fff!important}.background-white{background-color:#fff!important}.border-white{border-color:#fff!important}.text-black,.text-black>*{color:#000!important}.background-black{background-color:#000!important}.border-black{border-color:#000!important}.text-default,.text-default>*{color:#cfcfc4!important}.background-default{background-color:#cfcfc4!important}.border-default{border-color:#cfcfc4!important}.text-primary,.text-primary>*{color:#779ecb!important}.background-primary{background-color:#779ecb!important}.border-primary{border-color:#779ecb!important}.text-success,.text-success>*{color:#7d7!important}.background-success{background-color:#7d7!important}.border-success{border-color:#7d7!important}.text-info,.text-info>*{color:#9bddff!important}.background-info{background-color:#9bddff!important}.border-info{border-color:#9bddff!important}.text-warning,.text-warning>*{color:#ffb347!important}.background-warning{background-color:#ffb347!important}.border-warning{border-color:#ffb347!important}.text-danger,.text-danger>*{color:#ff6961!important}.background-danger{background-color:#ff6961!important}.border-danger{border-color:#ff6961!important}.responsive{width:100%;height:auto}.text-right{text-align:right}.text-left{text-align:left}.text-center{text-align:center}.list{list-style:none;margin:0;padding:0}.list>.list-item{clear:both;min-height:30px;height:auto;line-height:30px;overflow:auto;padding:10px;border:1px solid #cfcfc4;border-bottom:0 solid transparent}.list>.list-item>.badge{float:right;line-height:20px}.list>.list-item.list-item-white{color:#000;border-color:#fff;background-color:#fff}.list>.list-item.list-item-white *{color:#000}.list>.list-item.list-item-black{color:#fff;border-color:#333;background-color:#000}.list>.list-item.list-item-black *{color:#fff}.list>.list-item.list-item-default{color:#fff;border-color:#d9d9d0;background-color:#cfcfc4}.list>.list-item.list-item-default *{color:#fff}.list>.list-item.list-item-primary{color:#fff;border-color:#92b1d5;background-color:#779ecb}.list>.list-item.list-item-primary *{color:#fff}.list>.list-item.list-item-success{color:#fff;border-color:#92e492;background-color:#7d7}.list>.list-item.list-item-success *{color:#fff}.list>.list-item.list-item-info{color:#fff;border-color:#afe4ff;background-color:#9bddff}.list>.list-item.list-item-info *{color:#fff}.list>.list-item.list-item-warning{color:#fff;border-color:#ffc26c;background-color:#ffb347}.list>.list-item.list-item-warning *{color:#fff}.list>.list-item.list-item-danger{color:#fff;border-color:#ff8781;background-color:#ff6961}.list>.list-item.list-item-danger *{color:#fff}.list>.list-item:only-child{border-radius:5px!important}.list>.list-item:first-child{border-radius:5px 5px 0 0}.list>.list-item:last-child{border-bottom:1px solid #cfcfc4;border-radius:0 0 5px 5px}.list>.list-item>.list-item-right{float:right}.list>.list-item>.list-item-left{float:left}.modal{position:fixed;top:0;right:0;bottom:0;left:0;background:rgba(50,50,50,.6);z-index:99999;opacity:0;pointer-events:none}.modal.active,.modal.modal-active,.modal:target{opacity:1;pointer-events:auto}.modal.modal-absolute{position:absolute;z-index:1}.modal.modal-absolute>div{position:absolute}.modal>div{min-width:400px;max-width:90%;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:5px 20px 13px 20px;border-radius:0;background:#fff}.modal.modal-white>div{border-left:10px solid #fff;color:#000}.modal.modal-black>div{border-left:10px solid #000;color:#000}.modal.modal-default>div{border-left:10px solid #cfcfc4;color:#000}.modal.modal-primary>div{border-left:10px solid #779ecb;color:#000}.modal.modal-success>div{border-left:10px solid #7d7;color:#000}.modal.modal-info>div{border-left:10px solid #9bddff;color:#000}.modal.modal-warning>div{border-left:10px solid #ffb347;color:#000}.modal.modal-danger>div{border-left:10px solid #ff6961;color:#000}.modal-close{line-height:25px;position:absolute;right:5px;text-align:center;top:5px;width:24px;text-decoration:none}.navbar *{font-weight:300;display:inline-block;text-decoration:none}.navbar{position:relative;min-height:50px;width:100%;display:table}.navbar>.container{border-width:0 0 1px 0}.navbar.navbar-fixed{position:fixed;z-index:100000}.navbar.navbar-center>.container{border-width:0 1px 1px 1px;margin:0 auto;width:50%;position:relative}.navbar.navbar-center>.container>.navbar-content{margin:0}.navbar.border-white>.container{border-style:solid}.navbar.navbar-white>.container{background-color:#fff}.navbar.navbar-white>.container>.nav a.active,.navbar.navbar-white>.container>.nav a:hover,.navbar.navbar-white>.container>.nav a:target{background-color:#fff}.navbar.border-black>.container{border-style:solid}.navbar.navbar-black>.container{background-color:#000}.navbar.navbar-black>.container>.nav a.active,.navbar.navbar-black>.container>.nav a:hover,.navbar.navbar-black>.container>.nav a:target{background-color:#a6a6a6}.navbar.border-default>.container{border-style:solid}.navbar.navbar-default>.container{background-color:#cfcfc4}.navbar.navbar-default>.container>.nav a.active,.navbar.navbar-default>.container>.nav a:hover,.navbar.navbar-default>.container>.nav a:target{background-color:#eeeeea}.navbar.border-primary>.container{border-style:solid}.navbar.navbar-primary>.container{background-color:#779ecb}.navbar.navbar-primary>.container>.nav a.active,.navbar.navbar-primary>.container>.nav a:hover,.navbar.navbar-primary>.container>.nav a:target{background-color:#cfdded}.navbar.border-success>.container{border-style:solid}.navbar.navbar-success>.container{background-color:#7d7}.navbar.navbar-success>.container>.nav a.active,.navbar.navbar-success>.container>.nav a:hover,.navbar.navbar-success>.container>.nav a:target{background-color:#cff3cf}.navbar.border-info>.container{border-style:solid}.navbar.navbar-info>.container{background-color:#9bddff}.navbar.navbar-info>.container>.nav a.active,.navbar.navbar-info>.container>.nav a:hover,.navbar.navbar-info>.container>.nav a:target{background-color:#dcf3ff}.navbar.border-warning>.container{border-style:solid}.navbar.navbar-warning>.container{background-color:#ffb347}.navbar.navbar-warning>.container>.nav a.active,.navbar.navbar-warning>.container>.nav a:hover,.navbar.navbar-warning>.container>.nav a:target{background-color:#ffe4bf}.navbar.border-danger>.container{border-style:solid}.navbar.navbar-danger>.container{background-color:#ff6961}.navbar.navbar-danger>.container>.nav a.active,.navbar.navbar-danger>.container>.nav a:hover,.navbar.navbar-danger>.container>.nav a:target{background-color:#ffcbc8}.navbar>.container{clear:both;margin:0 auto 0 auto;display:table;width:100%;height:60px}.navbar>.container>.nav{float:right;margin:0 20px 0 0;display:inline-block}.navbar>.container>.nav>select{background-color:transparent}.navbar>.container>.nav>a{padding:20px 10px 22px 10px}.navbar>.container>.nav>a:active,.navbar>.container>.nav>a:hover,.navbar>.container>.nav>a:target{opacity:.6}.navbar .navbar-title{float:left;display:inline-block;margin:20px 0 0 20px}.panel{border:1px solid #cfcfc4;border-radius:5px}.panel.panel-white{border:1px solid #fff}.panel.panel-white .panel-footer,.panel.panel-white>.panel-heading{background-color:#fff;color:#000}.panel.panel-black{border:1px solid #000}.panel.panel-black .panel-footer,.panel.panel-black>.panel-heading{background-color:#000;color:#fff}.panel.panel-default{border:1px solid #cfcfc4}.panel.panel-default .panel-footer,.panel.panel-default>.panel-heading{background-color:#cfcfc4;color:#fff}.panel.panel-primary{border:1px solid #779ecb}.panel.panel-primary .panel-footer,.panel.panel-primary>.panel-heading{background-color:#779ecb;color:#fff}.panel.panel-success{border:1px solid #7d7}.panel.panel-success .panel-footer,.panel.panel-success>.panel-heading{background-color:#7d7;color:#fff}.panel.panel-info{border:1px solid #9bddff}.panel.panel-info .panel-footer,.panel.panel-info>.panel-heading{background-color:#9bddff;color:#fff}.panel.panel-warning{border:1px solid #ffb347}.panel.panel-warning .panel-footer,.panel.panel-warning>.panel-heading{background-color:#ffb347;color:#fff}.panel.panel-danger{border:1px solid #ff6961}.panel.panel-danger .panel-footer,.panel.panel-danger>.panel-heading{background-color:#ff6961;color:#fff}.panel .panel-footer,.panel .panel-heading,.panel>.panel-body{padding:15px}.panel>.panel-heading{top:0}.panel>.panel-footer{bottom:0}.tooltip,[data-tooltip]{position:relative;cursor:pointer}.tooltip:after,.tooltip:before,[data-tooltip]:after,[data-tooltip]:before{position:absolute;visibility:hidden;opacity:0;-webkit-transition:opacity .2s ease-in-out,visibility .2s ease-in-out,-webkit-transform .2s cubic-bezier(.71,1.7,.77,1.24);transition:opacity .2s ease-in-out,visibility .2s ease-in-out,-webkit-transform .2s cubic-bezier(.71,1.7,.77,1.24);transition:opacity .2s ease-in-out,visibility .2s ease-in-out,transform .2s cubic-bezier(.71,1.7,.77,1.24);transition:opacity .2s ease-in-out,visibility .2s ease-in-out,transform .2s cubic-bezier(.71,1.7,.77,1.24),-webkit-transform .2s cubic-bezier(.71,1.7,.77,1.24);-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);pointer-events:none}.tooltip:focus:after,.tooltip:focus:before,.tooltip:hover:after,.tooltip:hover:before,[data-tooltip]:focus:after,[data-tooltip]:focus:before,[data-tooltip]:hover:after,[data-tooltip]:hover:before{visibility:visible;opacity:1}.tooltip:before,[data-tooltip]:before{z-index:1001;border:6px solid transparent;background:0 0;content:""}.tooltip:after,[data-tooltip]:after{z-index:1000;padding:8px;min-width:160px;width:auto;background-color:#000;background-color:rgba(51,51,51,.9);color:#fff;content:attr(data-tooltip);font-size:14px;line-height:1.2}.tooltip-top:after,.tooltip-top:before,.tooltip:after,.tooltip:before,[data-tooltip]:after,[data-tooltip]:before{bottom:100%;left:50%}.tooltip-top:before,.tooltip:before,[data-tooltip]:before{margin-left:-6px;margin-bottom:-12px;border-top-color:#000;border-top-color:rgba(51,51,51,.9)}.tooltip-top:after,.tooltip:after,[data-tooltip]:after{margin-left:-80px}.tooltip-top:focus:after,.tooltip-top:focus:before,.tooltip-top:hover:after,.tooltip-top:hover:before,.tooltip:focus:after,.tooltip:focus:before,.tooltip:hover:after,.tooltip:hover:before,[data-tooltip]:focus:after,[data-tooltip]:focus:before,[data-tooltip]:hover:after,[data-tooltip]:hover:before{-webkit-transform:translateY(-12px);transform:translateY(-12px)}.tooltip-left:after,.tooltip-left:before{right:100%;bottom:50%;left:auto}.tooltip-left:before{margin-left:0;margin-right:-12px;margin-bottom:0;border-top-color:transparent;border-left-color:#000;border-left-color:rgba(51,51,51,.9)}.tooltip-left:focus:after,.tooltip-left:focus:before,.tooltip-left:hover:after,.tooltip-left:hover:before{-webkit-transform:translateX(-12px);transform:translateX(-12px)}.tooltip-bottom:after,.tooltip-bottom:before{top:100%;bottom:auto;left:50%}.tooltip-bottom:before{margin-top:-12px;margin-bottom:0;border-top-color:transparent;border-bottom-color:#000;border-bottom-color:rgba(51,51,51,.9)}.tooltip-bottom:focus:after,.tooltip-bottom:focus:before,.tooltip-bottom:hover:after,.tooltip-bottom:hover:before{-webkit-transform:translateY(12px);transform:translateY(12px)}.tooltip-right:after,.tooltip-right:before{bottom:50%;left:100%}.tooltip-right:before{margin-bottom:0;margin-left:-12px;border-top-color:transparent;border-right-color:#000;border-right-color:rgba(51,51,51,.9)}.tooltip-right:focus:after,.tooltip-right:focus:before,.tooltip-right:hover:after,.tooltip-right:hover:before{-webkit-transform:translateX(12px);transform:translateX(12px)}.tooltip-left:before,.tooltip-right:before{top:3px}.tooltip-left:after,.tooltip-right:after{margin-left:0;margin-bottom:-16px}[class^=tooltip-]{border-bottom:1px dotted #000;text-decoration:none}.progress{width:100%;border:1px solid #cfcfc4;text-align:center}.progress .progress-fill{font-size:16px;height:15px;padding:10px 0 10px 0;background-color:#779ecb}.progress .progress-fill.progress-fill-white{background-color:#fff;color:#000}.progress .progress-fill.progress-fill-white:hover{background-color:#fff}.progress .progress-fill.progress-fill-black{background-color:#000;color:#fff}.progress .progress-fill.progress-fill-black:hover{background-color:#404040}.progress .progress-fill.progress-fill-default{background-color:#cfcfc4;color:#fff}.progress .progress-fill.progress-fill-default:hover{background-color:#dbdbd3}.progress .progress-fill.progress-fill-primary{background-color:#779ecb;color:#fff}.progress .progress-fill.progress-fill-primary:hover{background-color:#99b6d8}.progress .progress-fill.progress-fill-success{background-color:#7d7;color:#fff}.progress .progress-fill.progress-fill-success:hover{background-color:#99e599}.progress .progress-fill.progress-fill-info{background-color:#9bddff;color:#fff}.progress .progress-fill.progress-fill-info:hover{background-color:#b4e6ff}.progress .progress-fill.progress-fill-warning{background-color:#ffb347;color:#fff}.progress .progress-fill.progress-fill-warning:hover{background-color:#ffc675}.progress .progress-fill.progress-fill-danger{background-color:#ff6961;color:#fff}.progress .progress-fill.progress-fill-danger:hover{background-color:#ff8f89}.spinner-overlay{position:relative;top:0;left:0;width:100%;height:100%;z-index:3}.spinner-wrapper{text-align:center;position:relative;top:calc(50% - 50px)}.spinner-wrapper>.spinner{min-height:30px;min-width:30px}.spinner-message{box-sizing:border-box;width:100%;margin-top:30px;text-align:center;font-weight:400;z-index:100;outline:0}.spinner{display:inline-block;min-height:20px;height:auto;min-width:20px;width:auto;background-color:transparent;animation:rotation .7s infinite linear;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid transparent;border-top:3px solid #2180c0;border-radius:100%}.spinner.spinner-absolute{position:absolute}.spinner.spinner-white{border-top:3px solid #fff}.spinner.spinner-white.spinner-done{border-color:#fff;border-width:3px 3px 3px 3px}.spinner.spinner-white.done:after{border-width:0 3px 0 3px}.spinner.spinner-black{border-top:3px solid #000}.spinner.spinner-black.spinner-done{border-color:#000;border-width:3px 3px 3px 3px}.spinner.spinner-black.done:after{border-width:0 3px 0 3px}.spinner.spinner-default{border-top:3px solid #cfcfc4}.spinner.spinner-default.spinner-done{border-color:#cfcfc4;border-width:3px 3px 3px 3px}.spinner.spinner-default.done:after{border-width:0 3px 0 3px}.spinner.spinner-primary{border-top:3px solid #779ecb}.spinner.spinner-primary.spinner-done{border-color:#779ecb;border-width:3px 3px 3px 3px}.spinner.spinner-primary.done:after{border-width:0 3px 0 3px}.spinner.spinner-success{border-top:3px solid #7d7}.spinner.spinner-success.spinner-done{border-color:#7d7;border-width:3px 3px 3px 3px}.spinner.spinner-success.done:after{border-width:0 3px 0 3px}.spinner.spinner-info{border-top:3px solid #9bddff}.spinner.spinner-info.spinner-done{border-color:#9bddff;border-width:3px 3px 3px 3px}.spinner.spinner-info.done:after{border-width:0 3px 0 3px}.spinner.spinner-warning{border-top:3px solid #ffb347}.spinner.spinner-warning.spinner-done{border-color:#ffb347;border-width:3px 3px 3px 3px}.spinner.spinner-warning.done:after{border-width:0 3px 0 3px}.spinner.spinner-danger{border-top:3px solid #ff6961}.spinner.spinner-danger.spinner-done{border-color:#ff6961;border-width:3px 3px 3px 3px}.spinner.spinner-danger.done:after{border-width:0 3px 0 3px}@-moz-keyframes rotation{from{transform:rotate(0)}to{transform:rotate(359deg)}}@-webkit-keyframes rotation{from{transform:rotate(0)}to{transform:rotate(359deg)}}@-o-keyframes rotation{from{transform:rotate(0)}to{transform:rotate(359deg)}}@keyframes rotation{from{transform:rotate(0)}to{transform:rotate(359deg)}}.table{text-align:center;word-break:break-all}.table.table-white{border:none}.table.table-white thead>tr>th{color:#fff}.table.table-white td,.table.table-white th{color:#fff;border-bottom:.1rem solid #fff}.table.table-black{border:none}.table.table-black thead>tr>th{color:#000}.table.table-black td,.table.table-black th{color:#0d0d0d;border-bottom:.1rem solid #000}.table.table-default{border:none}.table.table-default thead>tr>th{color:#cfcfc4}.table.table-default td,.table.table-default th{color:#d1d1c7;border-bottom:.1rem solid #cfcfc4}.table.table-primary{border:none}.table.table-primary thead>tr>th{color:#779ecb}.table.table-primary td,.table.table-primary th{color:#7ea3ce;border-bottom:.1rem solid #779ecb}.table.table-success{border:none}.table.table-success thead>tr>th{color:#7d7}.table.table-success td,.table.table-success th{color:#7edf7e;border-bottom:.1rem solid #7d7}.table.table-info{border:none}.table.table-info thead>tr>th{color:#9bddff}.table.table-info td,.table.table-info th{color:#a0dfff;border-bottom:.1rem solid #9bddff}.table.table-warning{border:none}.table.table-warning thead>tr>th{color:#ffb347}.table.table-warning td,.table.table-warning th{color:#ffb750;border-bottom:.1rem solid #ffb347}.table.table-danger{border:none}.table.table-danger thead>tr>th{color:#ff6961}.table.table-danger td,.table.table-danger th{color:#ff7069;border-bottom:.1rem solid #ff6961}.table thead>tr>th{font-weight:700}.table tbody tr:last-child>th{border-bottom:0}.table tfoot td:empty{padding:0}.table td,.table th{border-bottom:.1rem solid #e1e1e1;text-align:left;padding:10px}.table.responsive{border-collapse:collapse;border-spacing:0;display:table} -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const bodyParser = require('body-parser'); 2 | const acorn = require('acorn'); 3 | const estraverse = require('estraverse'); 4 | const escodegen = require('escodegen'); 5 | const JSONfn = require('json-fn'); 6 | const express = require('express'); 7 | const path = require('path'); 8 | const mongoose = require('mongoose'); 9 | const vm = require('vm'); 10 | const tmp = require('tmp'); 11 | const execSync = require('child_process').execSync; 12 | const app = express(); 13 | const port = process.env.PORT || 8080; 14 | 15 | mongoose.connect(process.env.MONGO_URL); 16 | const NotebookSchema = new mongoose.Schema({ notes: Object }, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }); 17 | const Notebook = mongoose.model('Notebook', NotebookSchema); 18 | 19 | app.use(express.static('dist')); 20 | 21 | app.use(bodyParser.urlencoded({ 22 | extended: false, 23 | verify: (req, res, buf) => { 24 | req.rawBody = buf 25 | } 26 | })); 27 | app.use(bodyParser.json({ 28 | verify: (req, res, buf) => { 29 | req.rawBody = buf 30 | } 31 | })); 32 | 33 | function parse(code) { 34 | var ast = acorn.parse(code); 35 | var parsed = estraverse.replace(ast, { 36 | enter: function (node) { 37 | if(node.type === 'CallExpression') { 38 | if(node.callee && node.callee.object && node.callee.object.name && node.callee.object.name == 'console' && node.callee.property) { 39 | node.callee.property.name = 'push'; 40 | } 41 | } 42 | } 43 | }); 44 | return escodegen.generate(parsed); 45 | } 46 | 47 | app.post('/api/notebook', (req, res) => { 48 | const notebook = req.body.notebook; 49 | 50 | Notebook.create((notebook || {}), (error, doc) => { 51 | if(error) { 52 | res.status(500); 53 | res.send(error); 54 | } else { 55 | res.status(200); 56 | res.send(doc); 57 | } 58 | }); 59 | }); 60 | 61 | app.put('/api/notebook', (req, res) => { 62 | const notebook = req.body.notebook; 63 | 64 | Notebook.findOneAndUpdate({ _id: notebook._id }, notebook, (error, doc) => { 65 | if(error) { 66 | res.status(500); 67 | res.send(error); 68 | } else { 69 | res.status(200); 70 | res.send(doc); 71 | } 72 | }); 73 | }); 74 | 75 | app.get('/api/notebook/:hash', (req, res) => { 76 | Notebook.findOne({ _id: req.params.hash }, (error, doc) => { 77 | if(error) { 78 | res.status(500); 79 | return res.send({ error }); 80 | } 81 | res.send({ notebook: doc }); 82 | }); 83 | }); 84 | 85 | app.post('/api/run', (req, res) => { 86 | const tmpDir = tmp.dirSync(); 87 | const results = {}; 88 | const runnable = req.body.runnable; 89 | const sandbox = { 90 | console: [], 91 | require: (name) => { 92 | execSync(`npm install ${name}`, { 93 | cwd: tmpDir.name 94 | }); 95 | return require(`${tmpDir.name}/node_modules/${name}`); 96 | } 97 | }; 98 | const context = new vm.createContext(sandbox, {}, { 99 | displayErrors: true 100 | }); 101 | 102 | Object.keys(runnable).forEach((key) => { 103 | try { 104 | const value = parse(runnable[key]); 105 | const script = new vm.Script(value); 106 | 107 | const start = new Date().getTime(); 108 | results[key] = { 109 | result: script.runInContext(context), 110 | context: Object.assign({}, context), 111 | ast: acorn.parse(value) 112 | }; 113 | // make sure we don't send the require code to client 114 | delete results[key]['context']['require']; 115 | 116 | // set the date 117 | results[key]['time'] = new Date().getTime() - start; 118 | 119 | // make sure the console resets 120 | context.console = []; 121 | } catch(ex) { 122 | results[key] = { 123 | error: ex.toString() 124 | } 125 | } 126 | }); 127 | res.status(200); 128 | res.send(JSONfn.stringify(results)); 129 | }); 130 | 131 | app.use((req, res) => { 132 | res.sendFile(path.resolve(__dirname, 'dist', 'index.html')); 133 | }); 134 | 135 | app.listen(port, () => { 136 | console.log(`node-notebook listening on port ${port}`); // eslint-disable-line 137 | }); 138 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-notebook", 3 | "version": "1.0.0", 4 | "description": "A notebook service that runs Javascript through the node vm", 5 | "author": "Gabriel J. Csapo ", 6 | "bugs": { 7 | "url": "https://github.com/gabrielcsapo/node-notebook/issues" 8 | }, 9 | "homepage": "https://github.com/gabrielcsapo/node-notebook#readme", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/gabrielcsapo/node-notebook.git" 13 | }, 14 | "bin": { 15 | "node-notebook": "./bin/index.js" 16 | }, 17 | "scripts": { 18 | "start": "./bin/index.js", 19 | "lint": "eslint .", 20 | "build": "NODE_ENV=production webpack --progress", 21 | "dev": "webpack-dev-server --hot" 22 | }, 23 | "license": "ISC", 24 | "dependencies": { 25 | "acorn": "^5.0.3", 26 | "body-parser": "^1.15.2", 27 | "commander": "^2.9.0", 28 | "escodegen": "^1.8.1", 29 | "estraverse": "^4.2.0", 30 | "express": "^4.14.0", 31 | "json-fn": "^1.1.1", 32 | "mongoose": "^4.9.9", 33 | "tmp": "0.0.31" 34 | }, 35 | "devDependencies": { 36 | "babel-core": "^6.24.1", 37 | "babel-loader": "^7.0.0", 38 | "babel-preset-es2015": "^6.24.1", 39 | "babel-preset-react": "^6.24.1", 40 | "brace": "^0.10.0", 41 | "css-loader": "^0.28.1", 42 | "eslint": "^3.8.1", 43 | "eslint-plugin-react": "^7.0.1", 44 | "prop-types": "^15.5.10", 45 | "psychic-ui": "^1.0.6", 46 | "react": "^15.5.4", 47 | "react-ace": "^4.2.1", 48 | "react-dom": "^15.5.4", 49 | "react-json-tree": "^0.10.9", 50 | "react-router": "^3.0.5", 51 | "style-loader": "^0.17.0", 52 | "webpack": "^2.5.1", 53 | "webpack-dev-server": "^2.4.5" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielcsapo/node-notebook/8003ce29ccb2be4090bfebf0d002a85ed5651aa9/screenshots/main.png -------------------------------------------------------------------------------- /screenshots/notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielcsapo/node-notebook/8003ce29ccb2be4090bfebf0d002a85ed5651aa9/screenshots/notebook.png -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import { render } from 'react-dom'; 2 | 3 | import 'psychic-ui/dist/psychic-min.css'; 4 | import './style.css'; 5 | 6 | import routes from './router'; 7 | 8 | const mountNode = document.querySelector('#root'); 9 | 10 | render(routes, mountNode); 11 | -------------------------------------------------------------------------------- /src/components/block.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import JSONTree from 'react-json-tree'; 3 | import AceEditor from 'react-ace'; 4 | import PropTypes from 'prop-types'; 5 | 6 | import 'brace/mode/javascript'; 7 | import 'brace/theme/github'; 8 | 9 | import Result from './result'; 10 | 11 | class Block extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | } 15 | onChange(value) { 16 | const { id, onChange } = this.props; 17 | 18 | onChange(id, value); 19 | } 20 | deleteBlock() { 21 | const { id, deleteBlock } = this.props; 22 | 23 | deleteBlock(id); 24 | } 25 | runBlock() { 26 | const { id, runBlock } = this.props; 27 | 28 | runBlock(id); 29 | } 30 | render() { 31 | const { content, returnValue, loading } = this.props; 32 | 33 | const { context, result, ast, error, time } = returnValue; 34 | 35 | if(loading) { 36 | return ( 37 |
38 |
39 |
40 |
41 |
42 | ) 43 | } 44 | 45 | return ( 46 |
47 |
48 | 56 |
57 |
58 | 59 | 60 |
61 | { time !== undefined ?
62 | Time 63 |
 64 |                 Took {time} ms
 65 |               
66 |
: ''} 67 | { error ?
68 | Error 69 |
 70 |                 { error }
 71 |               
72 |
: ''} 73 | { context && context.console ?
74 | Console 75 |
 76 |                     { context.console.map((value, i) => { return `${i}: ${value.toString()} \n`}) }
 77 |                 
78 |
: '' } 79 | { context ?
80 | Context 81 |
 82 |                      key !== 'console')
 84 |                       .reduce((obj, key) => {
 85 |                         obj[key] = context[key];
 86 |                         return obj;
 87 |                       }, {})} theme={{
 88 |                       scheme: 'monokai',
 89 |                       base00: 'rgba(#ffffff, 0)',
 90 |                       base01: '#383830',
 91 |                       base02: '#49483e',
 92 |                       base03: '#75715e',
 93 |                       base04: '#a59f85',
 94 |                       base05: '#f8f8f2',
 95 |                       base06: '#f5f4f1',
 96 |                       base07: '#f9f8f5',
 97 |                       base08: '#f92672',
 98 |                       base09: '#fd971f',
 99 |                       base0A: '#f4bf75',
100 |                       base0B: '#a6e22e',
101 |                       base0C: '#a1efe4',
102 |                       base0D: '#66d9ef',
103 |                       base0E: '#ae81ff',
104 |                       base0F: '#cc6633'
105 |                     }} invertTheme={true} />
106 |                 
107 |
: '' } 108 | { result ? : '' } 109 | { ast ?
110 | AST 111 |
112 |                   
131 |               
132 |
: ''} 133 |
134 | ); 135 | } 136 | } 137 | 138 | Block.defaultProps = { 139 | content: '', 140 | id: new Date(), 141 | onChange: () => {}, 142 | deleteBlock: () => {}, 143 | runBlock: () => {}, 144 | returnValue: {} 145 | }; 146 | 147 | Block.propTypes = { 148 | id: PropTypes.string, 149 | loading: PropTypes.boolean, 150 | content: PropTypes.string, 151 | onChange: PropTypes.func, 152 | deleteBlock: PropTypes.func, 153 | runBlock: PropTypes.func, 154 | returnValue: PropTypes.object 155 | }; 156 | 157 | export default Block; 158 | -------------------------------------------------------------------------------- /src/components/chart/axis.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class XAxis extends React.Component { 5 | render() { 6 | let { padding, height, width, maxValue } = this.props; 7 | let segment = height / 4; 8 | let lines = [1, 2, 3]; 9 | 10 | maxValue = ~~(maxValue / 4); 11 | 12 | return ( 13 | {lines.map((l, li) => { 14 | const y = ~~(l * segment + padding) + .5; 15 | return ( 16 | 24 | 25 | 30 | { maxValue * (3 - li) } 31 | 32 | 33 | ) 34 | })} 35 | ); 36 | } 37 | } 38 | 39 | XAxis.propTypes = { 40 | padding: PropTypes.number, 41 | height: PropTypes.number, 42 | width: PropTypes.number, 43 | maxValue: PropTypes.number, 44 | }; 45 | 46 | XAxis.defaultProps = { 47 | padding: 0, 48 | height: 0, 49 | width: 0, 50 | maxValue: 0 51 | }; 52 | 53 | class YAxis extends React.Component { 54 | render() { 55 | let { axis, padding, height, width } = this.props; 56 | let lines = [0, 1, 2, 3, 4]; 57 | let segment = width / 4; 58 | height = height + padding; 59 | 60 | return ( 61 | {lines.map((l, li) => { 62 | var x = ~~(li * segment + padding) + .5; 63 | return ( 64 | 69 | 70 | { axis[li % axis.length] } 71 | 72 | 73 | ) 74 | })} 75 | ); 76 | } 77 | } 78 | 79 | YAxis.propTypes = { 80 | padding: PropTypes.number, 81 | height: PropTypes.number, 82 | width: PropTypes.number, 83 | axis: PropTypes.array 84 | }; 85 | 86 | YAxis.defaultProps = { 87 | padding: 0, 88 | height: 0, 89 | width: 0, 90 | axis: [] 91 | }; 92 | 93 | module.exports = { 94 | YAxis, 95 | XAxis 96 | }; 97 | -------------------------------------------------------------------------------- /src/components/chart/curve.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class Curve extends React.Component { 5 | render() { 6 | let { points, width, height, padding, lines, area, color, stroke, updating } = this.props; 7 | let path = []; 8 | let areaPath = []; 9 | let style = { pointerEvents: 'none' }; 10 | let fn = lines === true ? 'L' : 'R'; 11 | 12 | height += padding; 13 | 14 | if (updating === true) { 15 | style['opacity'] = 0; 16 | style['transition'] = 'none'; 17 | } 18 | 19 | path = points.map((p, pi) => (pi === 0 ? '' : (pi === 1 ? fn : '')) + p[0] + ',' + p[1]); 20 | path = 'M' + path.join(' '); 21 | 22 | if (lines !== true) { 23 | path = parsePath(path, height).join(' '); 24 | } 25 | 26 | if (area === true) { 27 | areaPath = path.replace('M', 'L'); 28 | areaPath = 'M' + padding + ',' + height + areaPath; 29 | areaPath += 'L' + (width + padding) + ',' + height; 30 | } 31 | 32 | return ( 33 | 34 | { area === true ? : null } 35 | 36 | 37 | ); 38 | } 39 | } 40 | 41 | Curve.propTypes = { 42 | points: PropTypes.object, 43 | width: PropTypes.number, 44 | height: PropTypes.number, 45 | padding: PropTypes.number, 46 | lines: PropTypes.array, 47 | area: PropTypes.boolean, 48 | color: PropTypes.string, 49 | stroke: PropTypes.number, 50 | updating: PropTypes.updating 51 | }; 52 | 53 | module.exports = { 54 | Curve 55 | }; 56 | 57 | // Catmull-Rom to Bezier found here: http://jsdo.it/ynakajima/catmullrom2bezier 58 | // Whoever wrote this is AWESOME! Thank you! 59 | function parsePath(d, maxHeight) { 60 | var pathArray = [], lastX = '', lastY = ''; 61 | 62 | if ( -1 != d.search(/[rR]/) ) { 63 | // no need to redraw the path if no Catmull-Rom segments are found 64 | 65 | // split path into constituent segments 66 | var pathSplit = d.split(/([A-Za-z])/); 67 | for (var i = 0, iLen = pathSplit.length; iLen > i; i++) { 68 | var segment = pathSplit[i]; 69 | 70 | // make command code lower case, for easier matching 71 | // NOTE: this code assumes absolution coordinates, and doesn't account for relative command coordinates 72 | var command = segment.toLowerCase(); 73 | if ( -1 != segment.search(/[A-Za-z]/) ) { 74 | var val = ""; 75 | if ( "z" != command ) { 76 | i++; 77 | val = pathSplit[ i ].replace(/\s+$/, ''); 78 | } 79 | 80 | if ( "r" == command ) { 81 | // "R" and "r" are the a Catmull-Rom spline segment 82 | 83 | var points = lastX + "," + lastY + " " + val; 84 | 85 | // convert Catmull-Rom spline to Bézier curves 86 | var beziers = catmullRom2bezier( points, maxHeight ); 87 | //insert replacement curves back into array of path segments 88 | pathArray.push( beziers ); 89 | } else { 90 | // rejoin the command code and the numerical values, place in array of path segments 91 | pathArray.push( segment + val ); 92 | 93 | // find last x,y points, for feeding into Catmull-Rom conversion algorithm 94 | if ( "h" == command ) { 95 | lastX = val; 96 | } else if ( "v" == command ) { 97 | lastY = val; 98 | } else if ( "z" != command ) { 99 | var c = val.split(/[,\s]/); 100 | lastY = c.pop(); 101 | lastX = c.pop(); 102 | } 103 | } 104 | } 105 | } 106 | // recombine path segments and set new path description in DOM 107 | } 108 | 109 | return pathArray; 110 | } 111 | 112 | function catmullRom2bezier( points, maxHeight ) { 113 | var crp = points.split(/[,\s]/); 114 | 115 | var d = ""; 116 | for (var i = 0, iLen = crp.length; iLen - 2 > i; i+=2) { 117 | var p = []; 118 | if ( 0 == i ) { 119 | p.push( {x: parseFloat(crp[ i ]), y: parseFloat(crp[ i + 1 ])} ); 120 | p.push( {x: parseFloat(crp[ i ]), y: parseFloat(crp[ i + 1 ])} ); 121 | p.push( {x: parseFloat(crp[ i + 2 ]), y: parseFloat(crp[ i + 3 ])} ); 122 | p.push( {x: parseFloat(crp[ i + 4 ]), y: parseFloat(crp[ i + 5 ])} ); 123 | } else if ( iLen - 4 == i ) { 124 | p.push( {x: parseFloat(crp[ i - 2 ]), y: parseFloat(crp[ i - 1 ])} ); 125 | p.push( {x: parseFloat(crp[ i ]), y: parseFloat(crp[ i + 1 ])} ); 126 | p.push( {x: parseFloat(crp[ i + 2 ]), y: parseFloat(crp[ i + 3 ])} ); 127 | p.push( {x: parseFloat(crp[ i + 2 ]), y: parseFloat(crp[ i + 3 ])} ); 128 | } else { 129 | p.push( {x: parseFloat(crp[ i - 2 ]), y: parseFloat(crp[ i - 1 ])} ); 130 | p.push( {x: parseFloat(crp[ i ]), y: parseFloat(crp[ i + 1 ])} ); 131 | p.push( {x: parseFloat(crp[ i + 2 ]), y: parseFloat(crp[ i + 3 ])} ); 132 | p.push( {x: parseFloat(crp[ i + 4 ]), y: parseFloat(crp[ i + 5 ])} ); 133 | } 134 | 135 | // Catmull-Rom to Cubic Bezier conversion matrix 136 | // 0 1 0 0 137 | // -1/6 1 1/6 0 138 | // 0 1/6 1 -1/6 139 | // 0 0 1 0 140 | 141 | var bp = []; 142 | bp.push( { x: p[1].x, y: p[1].y } ); 143 | bp.push( { x: ((-p[0].x + 6*p[1].x + p[2].x) / 6), y: ((-p[0].y + 6*p[1].y + p[2].y) / 6)} ); 144 | bp.push( { x: ((p[1].x + 6*p[2].x - p[3].x) / 6), y: ((p[1].y + 6*p[2].y - p[3].y) / 6) } ); 145 | bp.push( { x: p[2].x, y: p[2].y } ); 146 | 147 | bp = bp.map(_ => { 148 | if (_.y > maxHeight) { 149 | _.y = maxHeight; 150 | } 151 | 152 | return _; 153 | }); 154 | 155 | d += "C" + bp[1].x + "," + bp[1].y + " " + bp[2].x + "," + bp[2].y + " " + bp[3].x + "," + bp[3].y + " "; 156 | } 157 | 158 | return d; 159 | } 160 | -------------------------------------------------------------------------------- /src/components/chart/index.js: -------------------------------------------------------------------------------- 1 | import LineChart from './line'; 2 | import PieChart from './pie'; 3 | 4 | module.exports = { 5 | LineChart, 6 | PieChart 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/chart/line.css: -------------------------------------------------------------------------------- 1 | .LineChart { 2 | position: relative; 3 | display: block; 4 | margin: 0 auto; 5 | } 6 | .LineChart--tooltip { 7 | display: block; 8 | position: absolute; 9 | padding: 10px; 10 | background-color: #fff; 11 | border: 1px solid #d0d0d0; 12 | border-radius: 2px; 13 | font-family: Sans-serif; 14 | font-size: 10px; 15 | pointer-events: none; 16 | -webkit-transform: translate(-50%, -100%); 17 | transform: translate(-50%, -100%); 18 | white-space: nowrap; 19 | } 20 | .LineChart--tooltip::before { 21 | content: ''; 22 | position: absolute; 23 | border: 6px solid transparent; 24 | border-top: 6px solid #d0d0d0; 25 | bottom: -12px; 26 | left: 50%; 27 | margin-left: -6px; 28 | } 29 | .LineChart--tooltip::after { 30 | content: ''; 31 | position: absolute; 32 | border: 5px solid transparent; 33 | border-top: 5px solid #fff; 34 | bottom: -10px; 35 | left: 50%; 36 | margin-left: -5px; 37 | } 38 | .LineChart--tooltip i { 39 | font-style: normal; 40 | } 41 | .LineChart--tooltip i::before { 42 | content: ': '; 43 | } 44 | .LineChart--axis { 45 | font-family: Sans-serif; 46 | font-size: 10px; 47 | fill: #ccc; 48 | } 49 | .LineChart--label { 50 | font-family: Sans-serif; 51 | font-size: 10px; 52 | } 53 | .LineChart circle { 54 | -webkit-transition: 0.3s ease-out; 55 | transition: 0.3s ease-out; 56 | } 57 | .LineChart circle:hover { 58 | r: 8px; 59 | } 60 | .LineChart g { 61 | -webkit-transition: 1.5s ease-out; 62 | transition: 1.5s ease-out; 63 | } 64 | -------------------------------------------------------------------------------- /src/components/chart/line.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Curve } from './curve'; 5 | import { XAxis, YAxis } from './axis'; 6 | import { Points } from './points'; 7 | import { Tooltip } from './tooltip'; 8 | 9 | import './line.css'; 10 | 11 | class LineChart extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | tooltip: false, 16 | value: '', 17 | dataSet: 0, 18 | index: 0, 19 | x: 0, 20 | y: 0, 21 | color: '', 22 | updating: false 23 | }; 24 | } 25 | 26 | componentWillReceiveProps() { 27 | this.setState({ updating: true }, this.endUpdate); 28 | } 29 | 30 | endUpdate() { 31 | setTimeout(() => { 32 | this.setState({ updating: false }); 33 | }, 300); 34 | } 35 | 36 | showTooltip(point, dataSetIndex, index) { 37 | this.setState({ 38 | updating: false, 39 | tooltip: true, 40 | value: point[2], 41 | dataSet: dataSetIndex, 42 | index: index, 43 | x: point[0], 44 | y: point[1], 45 | color: point[3] 46 | }); 47 | } 48 | 49 | hideTooltip() { 50 | this.setState({ 51 | tooltip: false, 52 | value: '', 53 | dataSet: 0, 54 | index: 0, 55 | x: 0, 56 | y: 0, 57 | color: '', 58 | updating: false 59 | }); 60 | } 61 | 62 | render() { 63 | const { updating, tooltip, value, x, y, color } = this.state; 64 | let { 65 | data, 66 | lines, 67 | area, 68 | dots, 69 | stroke, 70 | radius, 71 | grid, 72 | axis, 73 | width, 74 | height, 75 | colors, 76 | labels, 77 | hideLabels, 78 | maxValue, 79 | heightRatio, 80 | padding 81 | } = this.props; 82 | 83 | let dataSet = []; 84 | const size = data[0].length - 1; 85 | 86 | height = height || width * (9 / 16); 87 | 88 | // Calculate the maxValue 89 | dataSet = data.forEach(pts => { 90 | var max = Math.max.apply(null, pts); 91 | maxValue = max > maxValue ? max : maxValue; 92 | }); 93 | 94 | // Y ratio 95 | if (maxValue === 0) { 96 | heightRatio = 1; 97 | } else { 98 | heightRatio = height / maxValue; 99 | } 100 | 101 | // Calculate the coordinates 102 | dataSet = data.map((pts, di) => 103 | pts.map((pt, pi) => [ 104 | ~~((width / size) * pi + padding) + .5, // x 105 | ~~((heightRatio) * (maxValue - pt) + padding) + .5, // y 106 | pt, // value 107 | colors[di % colors.length] // color 108 | ] 109 | )); 110 | 111 | const svgOpts = { 112 | xmlns: 'http://www.w3.org/2000/svg', 113 | width: (width + padding * 2) + 'px', 114 | height: (height + 2 * padding) + 'px', 115 | viewBox: '0 0 ' + (width + 2 * padding) + ' ' + (height + 2 * padding) 116 | }; 117 | 118 | return ( 119 | 120 | { grid ? 121 | 122 | 123 | 124 | 125 | : null } 126 | 127 | { dataSet.map((p, pi) => 128 | 129 | 140 | 141 | 142 | 143 | )} 144 | 145 | { tooltip ? 146 | 153 | : null } 154 | ); 155 | } 156 | } 157 | 158 | LineChart.propTypes = { 159 | data: PropTypes.array, 160 | axis: PropTypes.array, 161 | colors: PropTypes.array, 162 | labels: PropTypes.array, 163 | lines: PropTypes.booean, 164 | area: PropTypes.boolean, 165 | dots: PropTypes.boolean, 166 | stroke: PropTypes.number, 167 | radius: PropTypes.number, 168 | height: PropTypes.number, 169 | width: PropTypes.number, 170 | grid: PropTypes.boolean, 171 | padding: PropTypes.number, 172 | heightRatio: PropTypes.number, 173 | maxValue: PropTypes.number, 174 | hideLabels: PropTypes.boolean 175 | } 176 | 177 | LineChart.defaultProps = { 178 | data: [], 179 | colors: ['#aaa', '#888'], 180 | labels: [], 181 | lines: true, 182 | area: true, 183 | dots: true, 184 | stroke: 1, 185 | radius: 3, 186 | grid: true, 187 | padding: 50, 188 | heightRatio: 1, 189 | maxValue: 0, 190 | hideLabels: false, 191 | height: 0, 192 | width: 400 193 | }; 194 | 195 | module.exports = LineChart; 196 | -------------------------------------------------------------------------------- /src/components/chart/pie.css: -------------------------------------------------------------------------------- 1 | svg { 2 | display: inline-block; 3 | vertical-align: middle; 4 | -webkit-transform-origin: 50% 50%; 5 | transform-origin: 50% 50%; 6 | -webkit-animation: scale .6s; 7 | animation: scale .6s; 8 | margin: 10px; 9 | } 10 | svg text { 11 | font-family: Helvetica, Arial, sans-serif; 12 | font-weight: bolder; 13 | font-size: 12px; 14 | } 15 | @-webkit-keyframes scale { 16 | from { 17 | -webkit-transform: scale(0.5); 18 | transform: scale(0.5); 19 | } 20 | to { 21 | -webkit-transform: scale(1); 22 | transform: scale(1); 23 | } 24 | } 25 | @keyframes scale { 26 | from { 27 | -webkit-transform: scale(0.5); 28 | transform: scale(0.5); 29 | } 30 | to { 31 | -webkit-transform: scale(1); 32 | transform: scale(1); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/chart/pie.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Tooltip } from './tooltip'; 5 | import { Slice } from './slice'; 6 | 7 | import './pie.css'; 8 | 9 | class Pie extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | tooltip: false, 14 | value: '', 15 | label: 0, 16 | x: 0, 17 | y: 0, 18 | color: '' 19 | }; 20 | } 21 | 22 | showTooltip(point) { 23 | this.setState({ 24 | tooltip: true, 25 | value: point[2], 26 | label: point[3], 27 | x: point[0], 28 | y: point[1], 29 | color: point[4] 30 | }); 31 | } 32 | 33 | hideTooltip() { 34 | this.setState({ 35 | tooltip: false, 36 | value: '', 37 | label: 0, 38 | x: 0, 39 | y: 0, 40 | color: '' 41 | }); 42 | } 43 | 44 | render() { 45 | const { colors, percent, labels, hole, radius, data, stroke, strokeWidth } = this.props; 46 | 47 | const colorsLength = colors.length; 48 | const diameter = radius * 2; 49 | 50 | let sum = data.reduce(function (carry, current) { return carry + current; }, 0); 51 | let _startAngle = 0; 52 | 53 | const opts = { 54 | width: diameter, 55 | height: diameter, 56 | viewBox: '0 0 ' + diameter + ' ' + diameter, 57 | xmlns:"http://www.w3.org/2000/svg", 58 | version:"1.1" 59 | }; 60 | 61 | return ( 62 | 63 | 64 | {data.map((slice, sliceIndex) => { 65 | const _nextAngle = _startAngle; 66 | const _angle = (slice / sum) * 360; 67 | const _percent = (slice / sum) * 100; 68 | _startAngle += _angle; 69 | 70 | return (); 87 | })} 88 | 89 | { this.state.tooltip ? 90 | 97 | : null } 98 | 99 | ); 100 | } 101 | } 102 | 103 | Pie.propTypes = { 104 | colors: PropTypes.array, 105 | data: PropTypes.array, 106 | percent: PropTypes.boolean, 107 | labels: PropTypes.boolean, 108 | hole: PropTypes.number, 109 | radius: PropTypes.number, 110 | stroke: PropTypes.string, 111 | strokeWidth: PropTypes.number 112 | }; 113 | 114 | Pie.defaultProps = { 115 | colors: [], 116 | data: [], 117 | percent: true, 118 | labels: true, 119 | hole: 0, 120 | radius: 0, 121 | stroke: '#fff', 122 | strokeWidth: 0 123 | }; 124 | 125 | module.exports = Pie; 126 | -------------------------------------------------------------------------------- /src/components/chart/point.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class Point extends React.Component { 5 | mouseEnter() { 6 | this.props.showTooltip(this.props.point, this.props.dataSetIndex, this.props.index); 7 | } 8 | 9 | mouseLeave() { 10 | this.props.hideTooltip(); 11 | } 12 | 13 | render() { 14 | const { point, stroke, radius } = this.props; 15 | const x = point[0]; 16 | const y = point[1]; 17 | const color = point[3]; 18 | 19 | return (); 29 | } 30 | } 31 | 32 | Point.propTypes = { 33 | point: PropTypes.array, 34 | stroke: PropTypes.string, 35 | radius: PropTypes.number, 36 | index: PropTypes.number, 37 | dataSetIndex: PropTypes.number, 38 | showTooltip: PropTypes.func, 39 | hideTooltip: PropTypes.func 40 | }; 41 | 42 | Point.defaultProps = { 43 | point: [], 44 | stroke: '#fff', 45 | radius: 0, 46 | index: 0, 47 | dataSetIndex: 0, 48 | showTooltip: () => {}, 49 | hideTooltip: () => {} 50 | }; 51 | 52 | module.exports = Point; 53 | -------------------------------------------------------------------------------- /src/components/chart/points.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Point from './point'; 5 | 6 | class Points extends React.Component { 7 | render() { 8 | let { points, dataSetIndex, showTooltip, hideTooltip, radius, stroke, label, dots, hideLabels } = this.props; 9 | 10 | let lastPoint = points[points.length - 1]; 11 | let color = lastPoint[3]; 12 | let x = lastPoint[0]; 13 | let y = lastPoint[1]; 14 | 15 | return ( 16 | 17 | { dots === true ? 18 | points.map((p, pi) => 19 | ) 29 | : null } 30 | 31 | { hideLabels !== true ? 32 | { label } 33 | : null } 34 | 35 | ); 36 | } 37 | } 38 | 39 | Points.propTypes = { 40 | points: PropTypes.object, 41 | dataSetIndex: PropTypes.number, 42 | showTooltip: PropTypes.func, 43 | hideTooltip: PropTypes.func, 44 | radius: PropTypes.number, 45 | stroke: PropTypes.string, 46 | label: PropTypes.string, 47 | dots: PropTypes.boolean, 48 | hideLabels: PropTypes.boolean 49 | }; 50 | 51 | Points.defaultProps = { 52 | points: {}, 53 | dataSetIndex: 0, 54 | showTooltip: () => {}, 55 | hideTooltip: () => {}, 56 | radius: 0, 57 | stroke: '#fff', 58 | label: '', 59 | dots: true, 60 | hideLabels: false 61 | }; 62 | 63 | module.exports = { 64 | Points 65 | }; 66 | -------------------------------------------------------------------------------- /src/components/chart/slice.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class Slice extends React.Component { 5 | getInitialState() { 6 | return { 7 | path: '', 8 | x: 0, 9 | y: 0 10 | }; 11 | } 12 | 13 | getAnglePoint(startAngle, endAngle, radius, x, y) { 14 | var x1, y1, x2, y2; 15 | 16 | x1 = x + radius * Math.cos(Math.PI * startAngle / 180); 17 | y1 = y + radius * Math.sin(Math.PI * startAngle / 180); 18 | x2 = x + radius * Math.cos(Math.PI * endAngle / 180); 19 | y2 = y + radius * Math.sin(Math.PI * endAngle / 180); 20 | 21 | return { x1, y1, x2, y2 }; 22 | } 23 | 24 | componentWillReceiveProps() { 25 | this.setState({ path: '' }); 26 | this.draw(); 27 | } 28 | 29 | componentDidMount() { 30 | this.draw(); 31 | } 32 | 33 | draw() { 34 | const { radius, hole, angle, showLabel, startAngle, trueHole } = this.props; 35 | 36 | let path = []; 37 | let left, right, top = {}; 38 | 39 | // Get angle points 40 | left = this.getAnglePoint(startAngle, startAngle + angle, radius, radius, radius); 41 | right = this.getAnglePoint(startAngle, startAngle + angle, radius - hole, radius, radius); 42 | 43 | path.push('M' + left.x1 + ',' + left.y1); 44 | path.push('A'+ radius +','+ radius + ' 0 ' + (angle > 180 ? 1 : 0) + ',1 ' + left.x2 + ',' + left.y2); 45 | path.push('L' + right.x2 + ',' + right.y2); 46 | path.push('A'+ (radius - hole) + ',' + (radius - hole) + ' 0 ' + (angle > 180 ? 1 : 0) + ',0 ' + right.x1 + ',' + right.y1); 47 | 48 | // Close 49 | path.push('Z'); 50 | 51 | this.setState({ path: path.join(' ') }); 52 | 53 | if (showLabel) { 54 | top = this.getAnglePoint(startAngle, startAngle + (angle / 2), (radius / 2 + trueHole / 2), radius, radius); 55 | 56 | this.setState({ 57 | x: top.x2, 58 | y: top.y2 59 | }); 60 | } 61 | } 62 | 63 | mouseEnter() { 64 | const { x, y } = this.state; 65 | const { value, percent, percentValue, fill } = this.props; 66 | 67 | this.props.showTooltip([x, y, value, percent ? percentValue + '%' : value, fill]); 68 | } 69 | 70 | mouseLeave() { 71 | this.props.hideTooltip(); 72 | } 73 | 74 | render() { 75 | const { path, x, y } = this.state; 76 | const { fill, stroke, strokeWidth, showLabel, percent, percentValue, value } = this.props; 77 | return ( 78 | 79 | 87 | { showLabel && percentValue > 5 ? 88 | 89 | { percent ? percentValue + '%' : value } 90 | 91 | : null } 92 | 93 | ); 94 | } 95 | } 96 | 97 | Slice.propTypes = { 98 | fill: PropTypes.string, 99 | stroke: PropTypes.number, 100 | strokeWidth: PropTypes.number, 101 | showLabel: PropTypes.boolean, 102 | percent: PropTypes.boolean, 103 | percentValue: PropTypes.number, 104 | value: PropTypes.number, 105 | radius: PropTypes.number, 106 | hole: PropTypes.number, 107 | angle: PropTypes.angle, 108 | startAngle: PropTypes.number, 109 | trueHole: PropTypes.boolean, 110 | showTooltip: PropTypes.func, 111 | hideTooltip: PropTypes.func 112 | }; 113 | 114 | Slice.defaultProps = { 115 | fill: '#fff', 116 | stroke: 3, 117 | strokeWidth: 3, 118 | showLabel: true, 119 | percent: true, 120 | percentValue: 0, 121 | value: 0, 122 | radius: 0, 123 | hole: 0, 124 | angle: 0, 125 | startAngle: 0, 126 | trueHole: true, 127 | showTooltip: () => {}, 128 | hideTooltip: () => {} 129 | }; 130 | 131 | module.exports = { 132 | Slice 133 | }; 134 | -------------------------------------------------------------------------------- /src/components/chart/tooltip.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class Tooltip extends React.Component { 5 | render() { 6 | const { value, label, x, y, color } = this.props; 7 | const style = { 8 | left: ~~x, 9 | top: ~~y 10 | }; 11 | 12 | return ( 13 | 14 | { label } 15 | { value } 16 | 17 | ); 18 | } 19 | } 20 | 21 | Tooltip.propTypes = { 22 | value: PropTypes.number, 23 | label: PropTypes.string, 24 | x: PropTypes.number, 25 | y: PropTypes.number, 26 | color: PropTypes.string 27 | }; 28 | 29 | Tooltip.defaultProps = { 30 | value: 0, 31 | label: '', 32 | x: 0, 33 | y: 0, 34 | color: '' 35 | }; 36 | 37 | module.exports = { 38 | Tooltip 39 | }; 40 | -------------------------------------------------------------------------------- /src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class Layout extends React.Component { 5 | render () { 6 | const { children } = this.props; 7 | 8 | return ( 9 |
10 |
11 |
12 | 17 |
18 | New 📓 19 | Source 20 |
21 |
22 |
23 |
24 | { children } 25 |
26 |
27 |
28 |
29 | node-notebook 30 |  by  31 | @gabrielcsapo 32 |
33 |
34 |
35 |
36 | ); 37 | } 38 | } 39 | 40 | Layout.propTypes = { 41 | children: PropTypes.object 42 | }; 43 | 44 | export default Layout; 45 | -------------------------------------------------------------------------------- /src/components/result.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { LineChart } from './chart'; 5 | import Table from './table'; 6 | 7 | class Result extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | renderOption: 'print' 12 | } 13 | } 14 | 15 | getType(result) { 16 | let type = typeof result; 17 | 18 | // make sure if it is an object we check if it is an array 19 | if(type == 'object' && Array.isArray(result)) { 20 | type = 'array'; 21 | } 22 | 23 | return type; 24 | } 25 | 26 | getRenderOptions(type, result) { 27 | var options = [ 'print' ]; 28 | if(result) { 29 | switch(type) { 30 | case 'array': 31 | switch(Array.isArray(result[0]) ? 'array' : typeof result[0]) { 32 | case 'object': 33 | options.push('table'); 34 | break; 35 | case 'array': 36 | options.push('lineChart'); 37 | break; 38 | } 39 | break; 40 | } 41 | } 42 | return options; 43 | } 44 | 45 | renderResult(result, renderOption) { 46 | switch(renderOption) { 47 | case 'print': 48 | return result.toString(); 49 | break; 50 | case 'table': 51 | return ; 52 | case 'lineChart': 53 | return ; 54 | break; 55 | } 56 | } 57 | 58 | changeRenderOption(event) { 59 | this.setState({ 60 | renderOption: event.target.value 61 | }); 62 | } 63 | 64 | render() { 65 | const { result } = this.props 66 | const { renderOption } = this.state; 67 | const type = this.getType(result); 68 | const options = this.getRenderOptions(type, result); 69 | 70 | return ( 71 |
72 |
73 | Result ( { type } ) 74 | 79 |
80 |
81 |             { this.renderResult(result, renderOption)}
82 |         
83 |
84 | ) 85 | } 86 | } 87 | 88 | Result.propTypes = { 89 | result: PropTypes.any 90 | }; 91 | 92 | export default Result; 93 | -------------------------------------------------------------------------------- /src/components/table.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Table extends React.Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | generateHeaders() { 9 | const { data } = this.props; 10 | 11 | return Object.keys(data[0]).map((key) => { 12 | return
; 13 | }); 14 | } 15 | 16 | generateRows() { 17 | const { data } = this.props; 18 | 19 | return data.map((row) => { 20 | var cells = Object.keys(row).map((key) => { 21 | return ; 22 | }); 23 | return { cells } ; 24 | }); 25 | } 26 | 27 | render() { 28 | return ( 29 |
{key} {row[key]}
30 | { this.generateHeaders() } 31 | { this.generateRows() } 32 |
33 | ); 34 | } 35 | } 36 | 37 | module.exports = Table; 38 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class Main extends React.Component { 4 | render() { 5 | return ( 6 |
7 |
8 | Hello 📖 9 |
10 |
11 | Start a New Notebook 12 |
13 |
14 | ); 15 | } 16 | } 17 | 18 | export default Main; 19 | -------------------------------------------------------------------------------- /src/notebook.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import JSONfn from 'json-fn'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import Block from './components/block'; 6 | import { LineChart } from './components/chart'; 7 | 8 | class Notebook extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | loading: true, 13 | notebook: {}, 14 | error: '' 15 | }; 16 | } 17 | componentWillMount() { 18 | const self = this; 19 | const xhr = new XMLHttpRequest(); 20 | const hash = this.props.params && this.props.params.hash; 21 | 22 | if(hash) { 23 | xhr.open("GET", `/api/notebook/${hash}`); 24 | xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 25 | xhr.onreadystatechange = function() { 26 | if (xhr.readyState == 4) { 27 | if(xhr.status == 200) { 28 | const returnValues = JSON.parse(xhr.responseText).notebook; 29 | returnValues.notes = returnValues.notes || {}; 30 | self.setState({ 31 | loading: false, 32 | notebook: returnValues 33 | }); 34 | } else { 35 | const returnValues = JSON.parse(xhr.responseText); 36 | self.setState({ 37 | loading: false, 38 | error: returnValues 39 | }); 40 | } 41 | } 42 | } 43 | xhr.send(); 44 | } else { 45 | xhr.open("POST", `/api/notebook`); 46 | xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 47 | xhr.onreadystatechange = function() { 48 | if (xhr.readyState == 4 && xhr.status == 200) { 49 | const returnValues = JSON.parse(xhr.responseText); 50 | returnValues.notes = {}; 51 | self.setState({ 52 | loading: false, 53 | notebook: returnValues 54 | }); 55 | if(history) { 56 | history.pushState({}, null, `/notebook/${returnValues._id}`); 57 | } 58 | } 59 | } 60 | xhr.send(); 61 | } 62 | } 63 | deleteBlock(id) { 64 | let { notebook } = this.state; 65 | 66 | notebook.notes = Object.keys(notebook.notes) 67 | .filter(key => key !== id) 68 | .reduce((obj, key) => { 69 | obj[key] = notebook.notes[key]; 70 | return obj; 71 | }, {}); 72 | 73 | this.setState({ 74 | notebook 75 | }); 76 | } 77 | runBlock(id) { 78 | const self = this; 79 | const { notebook } = this.state; 80 | const runnable = {} // the object that holds the runnable scripts 81 | const keys = Object.keys(notebook.notes); 82 | for(var noteIndex = 0; noteIndex <= keys.length - 1; noteIndex++) { 83 | if(keys[noteIndex] == id) { 84 | runnable[keys[noteIndex]] = notebook.notes[keys[noteIndex]].content; 85 | notebook.notes[keys[noteIndex]].loading = true; 86 | break; 87 | } else { 88 | runnable[keys[noteIndex]] = notebook.notes[keys[noteIndex]].content; 89 | notebook.notes[keys[noteIndex]].loading = true; 90 | } 91 | } 92 | // update loading state 93 | self.setState({ 94 | notebook 95 | }); 96 | 97 | var xhr = new XMLHttpRequest(); 98 | xhr.open("POST", "/api/run"); 99 | xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 100 | xhr.onreadystatechange = function() { 101 | if (xhr.readyState == 4 && xhr.status == 200) { 102 | const returnValues = JSONfn.parse(xhr.responseText); 103 | Object.keys(returnValues).forEach((key) => { 104 | notebook.notes[key].returnValue = returnValues[key]; 105 | notebook.notes[key].loading = false; 106 | }); 107 | self.setState({ 108 | notebook 109 | }); 110 | } 111 | } 112 | xhr.send(JSON.stringify({ 113 | runnable 114 | })); 115 | } 116 | onChange(id, value) { 117 | const { notebook } = this.state; 118 | 119 | notebook.notes[id].content = value; 120 | 121 | this.setState({ 122 | notebook 123 | }); 124 | } 125 | addNote() { 126 | const { notebook } = this.state; 127 | const id = Date(); 128 | 129 | notebook.notes[id] = { 130 | content: '', 131 | returnValue: '' 132 | }; 133 | 134 | this.setState({ 135 | notebook 136 | }); 137 | } 138 | saveNotebook() { 139 | const { notebook } = this.state; 140 | 141 | var xhr = new XMLHttpRequest(); 142 | xhr.open("PUT", "/api/notebook"); 143 | xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 144 | xhr.onreadystatechange = function() { 145 | if (xhr.readyState == 4 && xhr.status !== 200) { 146 | const returnValues = JSON.parse(xhr.responseText); 147 | self.setState({ 148 | error: returnValues 149 | }); 150 | } 151 | } 152 | xhr.send(JSONfn.stringify({ 153 | notebook 154 | })); 155 | } 156 | forkNotebook() { 157 | const { notebook } = this.state; 158 | 159 | var xhr = new XMLHttpRequest(); 160 | xhr.open("POST", "/api/notebook"); 161 | xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 162 | xhr.onreadystatechange = function() { 163 | if (xhr.readyState == 4) { 164 | if(xhr.status === 200) { 165 | const returnValues = JSON.parse(xhr.responseText); 166 | window.location.href = `/notebook/${returnValues._id}`; 167 | } else { 168 | const returnValues = JSON.parse(xhr.responseText); 169 | self.setState({ 170 | error: returnValues 171 | }); 172 | } 173 | } 174 | } 175 | delete notebook['_id']; 176 | 177 | xhr.send(JSONfn.stringify({ 178 | notebook 179 | })); 180 | } 181 | render() { 182 | const { notebook, error, loading } = this.state; 183 | const { notes } = notebook; 184 | 185 | if(loading) { 186 | return ( 187 |
188 |
189 | Loading notebook ... 190 |
191 |
192 | ) 193 | } 194 | if(error) { 195 | return ( 196 |
197 |
198 | Error when retrieving document 🙈 199 |
200 |
201 | Try Creating a New Notebook 202 |
203 |
204 | ) 205 | } 206 | 207 | const times = Object.keys(notes).map((n => { 208 | return notes[n].returnValue && notes[n].returnValue.time; 209 | }), []); 210 | const opt = { 211 | data: [times], 212 | colors: ['#9a8585', '#a7daff', '#f7ca97'], 213 | labels: ['Time'], 214 | width: (window.innerWidth / 2) - 100, 215 | height: 200, 216 | lines: true, 217 | area: true, 218 | dots: true, 219 | hideLabels: false, 220 | grid: true 221 | }; 222 | 223 | return ( 224 |
225 |
226 |               {window.location.href}
227 |             
228 |
229 | 230 | 231 |
232 | { times.length > 0 ? : '' } 233 | { notes && Object.keys(notes).length > 0 ? Object.keys(notes).map((id) => { 234 | return 235 | }) :
You currently have no notes, press add note to get some!
} 236 |
237 | 238 | 239 |
240 |
241 | ); 242 | } 243 | } 244 | 245 | Notebook.propTypes = { 246 | params: PropTypes.objectOf({ 247 | hash: PropTypes.string 248 | }) 249 | } 250 | 251 | export default Notebook; 252 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Router, Route, browserHistory } from 'react-router'; 3 | 4 | import Layout from './components/layout'; 5 | import Main from './main'; 6 | import Notebook from './notebook'; 7 | 8 | export default ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | body { 5 | position: relative; 6 | margin: 0; 7 | font-family: 'Open Sans', sans-serif; 8 | } 9 | html, body, div#root { 10 | height: 100%; 11 | width: 100%; 12 | } 13 | .ace_editor > * { 14 | font-family: monospace; 15 | } 16 | .footer { 17 | text-align: center; 18 | } 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | const config = { 5 | entry: './src/app.js', 6 | output: { 7 | path: path.resolve(__dirname, 'dist'), 8 | filename: 'build.js' 9 | }, 10 | devServer: { 11 | port: 5000, 12 | proxy: { 13 | "/api/**": "http://localhost:8080" 14 | }, 15 | contentBase: 'dist', 16 | inline: true, 17 | historyApiFallback: true 18 | }, 19 | module: { 20 | loaders: [{ 21 | test: /\.css$/, 22 | loaders: ['style-loader', 'css-loader'] 23 | }, 24 | { 25 | test: /.jsx?$/, 26 | loader: 'babel-loader', 27 | exclude: /node_modules/, 28 | query: { 29 | presets: ['es2015', 'react'] 30 | } 31 | } 32 | ] 33 | }, 34 | plugins: [] 35 | }; 36 | 37 | if (process.env.NODE_ENV == 'production') { 38 | config.plugins = [ 39 | new webpack.DefinePlugin({ 40 | 'process.env': { 41 | 'NODE_ENV': JSON.stringify('production') 42 | } 43 | }), 44 | new webpack.ContextReplacementPlugin(/moment[\\\/]locale$/, /^\.\/(en)$/), 45 | new webpack.optimize.UglifyJsPlugin({ 46 | comments: false, 47 | compress: { 48 | unused: true, 49 | dead_code: true, 50 | warnings: false, 51 | drop_debugger: true, 52 | conditionals: true, 53 | evaluate: true, 54 | sequences: true, 55 | booleans: true, 56 | } 57 | }), 58 | new webpack.optimize.AggressiveMergingPlugin(), 59 | ]; 60 | } 61 | 62 | module.exports = config; 63 | --------------------------------------------------------------------------------