From bdd5a5fcffa5e7aa0a103506d0c8a0f9bfaaffd5 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 8 Mar 2016 20:40:06 +0100 Subject: [PATCH] Release 0.7.0 --- app/components/Bin/index.js | 12 +- app/components/CodeEditor/html-lint.js | 30 ++ app/components/CodeEditor/index.js | 60 +++ app/components/CodeEditor/json-lint.js | 14 + app/components/CodeEditor/linters/json.js | 416 ++++++++++++++++++ app/components/CodeEditor/styles.css | 4 +- app/components/Loaders/JsonConfig/index.js | 24 + app/components/Loaders/RawConfig/index.js | 23 + app/components/Loaders/index.js | 6 +- app/components/Toolbar/index.js | 7 +- app/components/Welcome/index.js | 28 ++ app/components/Welcome/styles.css | 46 ++ app/modules/Bin/actions/addFile.js | 13 + app/modules/Bin/actions/setDefaultBin.js | 23 - app/modules/Bin/actions/setEmptyBin.js | 13 + app/modules/Bin/actions/setWelcomeBin.js | 65 +++ app/modules/Bin/actions/shouldSave.js | 17 - app/modules/Bin/actions/toggleLoader.js | 4 +- .../runClicked.js => factories/runBin.js} | 2 +- app/modules/Bin/index.js | 9 +- app/modules/Bin/signals/boilerplateClicked.js | 4 +- app/modules/Bin/signals/emptyBinClicked.js | 7 + app/modules/Bin/signals/fileClicked.js | 1 - app/modules/Bin/signals/linted.js | 11 +- app/modules/Bin/signals/opened.js | 6 +- app/modules/Bin/signals/rootRouted.js | 4 +- .../Bin/signals/saveShortcutPressed.js | 20 +- app/modules/Bin/signals/welcomeBinClicked.js | 9 + app/modules/Live/signals/liveToggled.js | 4 +- index.html | 45 +- package.json | 7 +- server/createLoaders.js | 18 + server/index.js | 4 +- server/live.js | 4 + 34 files changed, 865 insertions(+), 95 deletions(-) create mode 100644 app/components/CodeEditor/html-lint.js create mode 100644 app/components/CodeEditor/json-lint.js create mode 100644 app/components/CodeEditor/linters/json.js create mode 100644 app/components/Loaders/JsonConfig/index.js create mode 100644 app/components/Loaders/RawConfig/index.js create mode 100644 app/components/Welcome/index.js create mode 100644 app/components/Welcome/styles.css delete mode 100644 app/modules/Bin/actions/setDefaultBin.js create mode 100644 app/modules/Bin/actions/setEmptyBin.js create mode 100644 app/modules/Bin/actions/setWelcomeBin.js delete mode 100644 app/modules/Bin/actions/shouldSave.js rename app/modules/Bin/{signals/runClicked.js => factories/runBin.js} (96%) create mode 100644 app/modules/Bin/signals/emptyBinClicked.js create mode 100644 app/modules/Bin/signals/welcomeBinClicked.js diff --git a/app/components/Bin/index.js b/app/components/Bin/index.js index 5c517a8..7f98ba0 100644 --- a/app/components/Bin/index.js +++ b/app/components/Bin/index.js @@ -7,6 +7,7 @@ import Preview from '../Preview'; import Log from '../Log'; import LiveUsers from '../LiveUsers'; import LiveUser from '../LiveUser'; +import Welcome from '../Welcome'; @Cerebral({ snackbar: 'bin.snackbar', @@ -14,7 +15,8 @@ import LiveUser from '../LiveUser'; isLoadingBin: 'bin.isLoadingBin', showLog: 'bin.showLog', showLoadingBin: 'bin.showLoadingBin', - live: 'live' + live: 'live', + showWelcome: 'bin.showWelcome' }) class Bin extends React.Component { constructor(props) { @@ -23,6 +25,9 @@ class Bin extends React.Component { showSnackbar: false }; } + componentWillMount() { + document.querySelector('#loader').style.display = 'none'; + } componentDidMount() { window.addEventListener('keydown', (event) => { if ((event.metaKey || event.ctrlKey) && event.keyCode === 83) { @@ -65,6 +70,11 @@ class Bin extends React.Component { }, 4000); } render() { + + if (this.props.showWelcome) { + return ; + } + return (
this.props.signals.bin.appClicked()}> diff --git a/app/components/CodeEditor/html-lint.js b/app/components/CodeEditor/html-lint.js new file mode 100644 index 0000000..15e2133 --- /dev/null +++ b/app/components/CodeEditor/html-lint.js @@ -0,0 +1,30 @@ +const defaultRules = { + 'tagname-lowercase': true, + 'attr-lowercase': true, + 'attr-value-double-quotes': true, + 'doctype-first': false, + 'tag-pair': true, + 'spec-char-escape': true, + 'id-unique': true, + 'src-not-empty': true, + 'attr-no-duplication': true +}; + +module.exports = function (CodeMirror, HTMLHint) { + return function(text, options) { + const found = []; + + const messages = HTMLHint.verify(text, options && options.rules || defaultRules); + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + var startLine = message.line - 1, endLine = message.line - 1, startCol = message.col - 1, endCol = message.col; + found.push({ + from: CodeMirror.Pos(startLine, startCol), + to: CodeMirror.Pos(endLine, endCol), + message: message.message, + severity : message.type + }); + } + return found; + }; +} diff --git a/app/components/CodeEditor/index.js b/app/components/CodeEditor/index.js index 4c34ad8..cbf8ef9 100755 --- a/app/components/CodeEditor/index.js +++ b/app/components/CodeEditor/index.js @@ -96,6 +96,10 @@ class CodeEditor extends React.Component { return 'text/x-less'; case 'scss': return 'text/x-sass'; + case 'html': + return 'htmlmixed'; + case 'json': + return 'application/json'; default: return 'jsx'; } @@ -252,6 +256,62 @@ class CodeEditor extends React.Component { } + if (mode === 'htmlmixed') { + + const setHtmlModeAndLinter = function () { + loadedLinters.push(mode); + require('codemirror/mode/htmlmixed/htmlmixed.js'); + require('codemirror/addon/edit/matchtags.js'); + require('codemirror/addon/edit/closetag.js'); + const htmlhint = require('htmlhint'); + const linter = require('./html-lint.js'); + this.codemirror.setOption('lint', { + getAnnotations: linter(CodeMirror, htmlhint.HTMLHint), + onUpdateLinting: this.onUpdateLinting + }); + this.codemirror.setOption('mode', mode); + this.setEditorValue(this.codemirror.getValue()); + }.bind(this); + + if (loadedLinters.indexOf(mode) >= 0) { + setHtmlModeAndLinter(); + } else { + this.props.signals.bin.linterRequested(); + return require.ensure([], () => { + setHtmlModeAndLinter(); + this.props.signals.bin.linterLoaded(); + }); + } + + } + + if (mode === 'application/json') { + + const setJsonModeAndLinter = function () { + loadedLinters.push(mode); + require('codemirror/mode/javascript/javascript.js'); + const jsonLint = require('./linters/json.js'); + const linter = require('./json-lint.js'); + this.codemirror.setOption('lint', { + getAnnotations: linter(CodeMirror, jsonLint), + onUpdateLinting: this.onUpdateLinting + }); + this.codemirror.setOption('mode', mode); + this.setEditorValue(this.codemirror.getValue()); + }.bind(this); + + if (loadedLinters.indexOf(mode) >= 0) { + setJsonModeAndLinter(); + } else { + this.props.signals.bin.linterRequested(); + return require.ensure([], () => { + setJsonModeAndLinter(); + this.props.signals.bin.linterLoaded(); + }); + } + + } + this.codemirror.setOption('mode', mode); return false; diff --git a/app/components/CodeEditor/json-lint.js b/app/components/CodeEditor/json-lint.js new file mode 100644 index 0000000..a1ab579 --- /dev/null +++ b/app/components/CodeEditor/json-lint.js @@ -0,0 +1,14 @@ +module.exports = function (CodeMirror, jsonlint) { + CodeMirror.registerHelper("lint", "json", function(text) { + var found = []; + jsonlint.parseError = function(str, hash) { + var loc = hash.loc; + found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column), + to: CodeMirror.Pos(loc.last_line - 1, loc.last_column), + message: str}); + }; + try { jsonlint.parse(text); } + catch(e) {} + return found; + }); +} diff --git a/app/components/CodeEditor/linters/json.js b/app/components/CodeEditor/linters/json.js new file mode 100644 index 0000000..80e1b7f --- /dev/null +++ b/app/components/CodeEditor/linters/json.js @@ -0,0 +1,416 @@ +/* Jison generated parser */ +var jsonlint = (function(){ +var parser = {trace: function trace() { }, +yy: {}, +symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1}, +terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"}, +productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]], +performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + +var $0 = $$.length - 1; +switch (yystate) { +case 1: // replace escaped characters with actual character + this.$ = yytext.replace(/\\(\\|")/g, "$"+"1") + .replace(/\\n/g,'\n') + .replace(/\\r/g,'\r') + .replace(/\\t/g,'\t') + .replace(/\\v/g,'\v') + .replace(/\\f/g,'\f') + .replace(/\\b/g,'\b'); + +break; +case 2:this.$ = Number(yytext); +break; +case 3:this.$ = null; +break; +case 4:this.$ = true; +break; +case 5:this.$ = false; +break; +case 6:return this.$ = $$[$0-1]; +break; +case 13:this.$ = {}; +break; +case 14:this.$ = $$[$0-1]; +break; +case 15:this.$ = [$$[$0-2], $$[$0]]; +break; +case 16:this.$ = {}; this.$[$$[$0][0]] = $$[$0][1]; +break; +case 17:this.$ = $$[$0-2]; $$[$0-2][$$[$0][0]] = $$[$0][1]; +break; +case 18:this.$ = []; +break; +case 19:this.$ = $$[$0-1]; +break; +case 20:this.$ = [$$[$0]]; +break; +case 21:this.$ = $$[$0-2]; $$[$0-2].push($$[$0]); +break; +} +}, +table: [{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}], +defaultActions: {16:[2,6]}, +parseError: function parseError(str, hash) { + throw new Error(str); +}, +parse: function parse(input) { + var self = this, + stack = [0], + vstack = [null], // semantic value stack + lstack = [], // location stack + table = this.table, + yytext = '', + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1; + + //this.reductionCount = this.shiftCount = 0; + + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + if (typeof this.lexer.yylloc == 'undefined') + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + + if (typeof this.yy.parseError === 'function') + this.parseError = this.yy.parseError; + + function popStack (n) { + stack.length = stack.length - 2*n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + + function lex() { + var token; + token = self.lexer.lex() || 1; // $end = 1 + // if token isn't its numeric value, convert + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + } + + var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected; + while (true) { + // retreive state number from top of stack + state = stack[stack.length-1]; + + // use default actions if available + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol == null) + symbol = lex(); + // read action for current state and first input + action = table[state] && table[state][symbol]; + } + + // handle parse error + _handle_error: + if (typeof action === 'undefined' || !action.length || !action[0]) { + + if (!recovering) { + // Report error + expected = []; + for (p in table[state]) if (this.terminals_[p] && p > 2) { + expected.push("'"+this.terminals_[p]+"'"); + } + var errStr = ''; + if (this.lexer.showPosition) { + errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'"; + } else { + errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " + + (symbol == 1 /*EOF*/ ? "end of input" : + ("'"+(this.terminals_[symbol] || symbol)+"'")); + } + this.parseError(errStr, + {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + + // just recovered from another error + if (recovering == 3) { + if (symbol == EOF) { + throw new Error(errStr || 'Parsing halted.'); + } + + // discard current lookahead and grab another + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + symbol = lex(); + } + + // try to recover from error + while (1) { + // check for error recovery rule in this state + if ((TERROR.toString()) in table[state]) { + break; + } + if (state == 0) { + throw new Error(errStr || 'Parsing halted.'); + } + popStack(1); + state = stack[stack.length-1]; + } + + preErrorSymbol = symbol; // save the lookahead token + symbol = TERROR; // insert generic error symbol as new lookahead + state = stack[stack.length-1]; + action = table[state] && table[state][TERROR]; + recovering = 3; // allow 3 real symbols to be shifted before reporting a new error + } + + // this shouldn't happen, unless resolve defaults are off + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol); + } + + switch (action[0]) { + + case 1: // shift + //this.shiftCount++; + + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); // push state + symbol = null; + if (!preErrorSymbol) { // normal execution/no error + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { // error just occurred, resume old lookahead f/ before error + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + + case 2: // reduce + //this.reductionCount++; + + len = this.productions_[action[1]][1]; + + // perform semantic action + yyval.$ = vstack[vstack.length-len]; // default to $$ = $1 + // default location, uses first token for firsts, last for lasts + yyval._$ = { + first_line: lstack[lstack.length-(len||1)].first_line, + last_line: lstack[lstack.length-1].last_line, + first_column: lstack[lstack.length-(len||1)].first_column, + last_column: lstack[lstack.length-1].last_column + }; + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + + if (typeof r !== 'undefined') { + return r; + } + + // pop off stack + if (len) { + stack = stack.slice(0,-1*len*2); + vstack = vstack.slice(0, -1*len); + lstack = lstack.slice(0, -1*len); + } + + stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce) + vstack.push(yyval.$); + lstack.push(yyval._$); + // goto new state = table[STATE][NONTERMINAL] + newState = table[stack[stack.length-2]][stack[stack.length-1]]; + stack.push(newState); + break; + + case 3: // accept + return true; + } + + } + + return true; +}}; +/* Jison generated lexer */ +var lexer = (function(){ +var lexer = ({EOF:1, +parseError:function parseError(str, hash) { + if (this.yy.parseError) { + this.yy.parseError(str, hash); + } else { + throw new Error(str); + } + }, +setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + return this; + }, +input:function () { + var ch = this._input[0]; + this.yytext+=ch; + this.yyleng++; + this.match+=ch; + this.matched+=ch; + var lines = ch.match(/\n/); + if (lines) this.yylineno++; + this._input = this._input.slice(1); + return ch; + }, +unput:function (ch) { + this._input = ch + this._input; + return this; + }, +more:function () { + this._more = true; + return this; + }, +less:function (n) { + this._input = this.match.slice(n) + this._input; + }, +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + tempMatch, + index, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/\n.*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length} + this.yytext += match[0]; + this.match += match[0]; + this.yyleng = this.yytext.length; + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); + if (this.done && this._input) this.done = false; + if (token) return token; + else return; + } + if (this._input === "") { + return this.EOF; + } else { + this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, +lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +popState:function popState() { + return this.conditionStack.pop(); + }, +_currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, +topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, +pushState:function begin(condition) { + this.begin(condition); + }}); +lexer.options = {}; +lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START +switch($avoiding_name_collisions) { +case 0:/* skip whitespace */ +break; +case 1:return 6 +break; +case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4 +break; +case 3:return 17 +break; +case 4:return 18 +break; +case 5:return 23 +break; +case 6:return 24 +break; +case 7:return 22 +break; +case 8:return 21 +break; +case 9:return 10 +break; +case 10:return 11 +break; +case 11:return 8 +break; +case 12:return 14 +break; +case 13:return 'INVALID' +break; +} +}; +lexer.rules = [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/]; +lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}}; + + +; +return lexer;})() +parser.lexer = lexer; +return parser; +})(); + +module.exports = jsonlint; diff --git a/app/components/CodeEditor/styles.css b/app/components/CodeEditor/styles.css index f8cc403..093f394 100644 --- a/app/components/CodeEditor/styles.css +++ b/app/components/CodeEditor/styles.css @@ -1,5 +1,7 @@ .wrapper { - flex: 1; + max-width: 60%; + flex-basis: 0; /* default value */ + flex-grow: 1; height: 100%; background-color: #202634; border-right: 2px solid #232e3c; diff --git a/app/components/Loaders/JsonConfig/index.js b/app/components/Loaders/JsonConfig/index.js new file mode 100644 index 0000000..175d39c --- /dev/null +++ b/app/components/Loaders/JsonConfig/index.js @@ -0,0 +1,24 @@ +import React from 'react'; +import {Decorator as Cerebral} from 'cerebral-view-react'; +import styles from '../styles.css'; +import currentLoader from '../../../computed/currentLoader'; + +@Cerebral({ + loader: currentLoader +}) +class JsonConfig extends React.Component { + render() { + const loader = this.props.loader; + + return ( +
+
+ With the .json extension you can load JSON files as parsed javascript + into your code +
+
+ ); + } +} + + export default JsonConfig; diff --git a/app/components/Loaders/RawConfig/index.js b/app/components/Loaders/RawConfig/index.js new file mode 100644 index 0000000..0b95bb8 --- /dev/null +++ b/app/components/Loaders/RawConfig/index.js @@ -0,0 +1,23 @@ +import React from 'react'; +import {Decorator as Cerebral} from 'cerebral-view-react'; +import styles from '../styles.css'; +import currentLoader from '../../../computed/currentLoader'; + +@Cerebral({ + loader: currentLoader +}) +class RawConfig extends React.Component { + render() { + const loader = this.props.loader; + + return ( +
+
+ With the .html extension you can load raw HTML into your code +
+
+ ); + } +} + + export default RawConfig; diff --git a/app/components/Loaders/index.js b/app/components/Loaders/index.js index 0ea0b14..df4869d 100644 --- a/app/components/Loaders/index.js +++ b/app/components/Loaders/index.js @@ -6,12 +6,16 @@ import BabelConfig from './BabelConfig'; import CssConfig from './CssConfig'; import TypescriptConfig from './TypescriptConfig'; import CoffeescriptConfig from './CoffeescriptConfig'; +import RawConfig from './RawConfig'; +import JsonConfig from './JsonConfig'; const loaders = { babel: BabelConfig, css: CssConfig, typescript: TypescriptConfig, - coffeescript: CoffeescriptConfig + coffeescript: CoffeescriptConfig, + raw: RawConfig, + json: JsonConfig }; @Cerebral({ diff --git a/app/components/Toolbar/index.js b/app/components/Toolbar/index.js index 2ee1925..164593f 100644 --- a/app/components/Toolbar/index.js +++ b/app/components/Toolbar/index.js @@ -78,7 +78,7 @@ class Toolbar extends React.Component { signals.bin.runClicked()}/>
@@ -87,7 +87,7 @@ class Toolbar extends React.Component { active={this.props.showLog} icon={icons.assignment} notify={this.props.shouldCheckLog} - disabled={this.props.isRunning || !this.props.isValid} + disabled={this.props.isRunning} onClick={() => signals.bin.logToggled()}/>
@@ -97,8 +97,7 @@ class Toolbar extends React.Component { icon={icons.live} disabled={ (!this.props.currentBin.isOwner && this.props.currentBin.author) || - this.props.isRunning || - !this.props.isValid + this.props.isRunning } onClick={() => signals.live.liveToggled()}/>
diff --git a/app/components/Welcome/index.js b/app/components/Welcome/index.js new file mode 100644 index 0000000..d2cc5c8 --- /dev/null +++ b/app/components/Welcome/index.js @@ -0,0 +1,28 @@ +import React from 'react'; +import {Decorator as Cerebral} from 'cerebral-view-react'; +import styles from './styles.css'; + +@Cerebral() +class Welcome extends React.Component { + render() { + return ( +
+
+

Welcome to WebpackBin

+

So what do you want to do?

+
this.props.signals.bin.welcomeBinClicked()}> + Load the welcome BIN +
+
this.props.signals.bin.emptyBinClicked()}> + Load an empty BIN +
+
+
+ +
+
+ ); + } +} + + export default Welcome; diff --git a/app/components/Welcome/styles.css b/app/components/Welcome/styles.css new file mode 100644 index 0000000..38eed80 --- /dev/null +++ b/app/components/Welcome/styles.css @@ -0,0 +1,46 @@ +.wrapper { + position: absolute; + top: 50%; + left: 50%; + width: 1200px; + height: 600px; + margin-left: -600px; + margin-top: -300px; + text-align: center; + display: flex; +} + +.column { + flex: 1; +} + +.wrapper h1 { + font-size: 60px; + margin-bottom: 0; +} + +.wrapper h3 { + color: rgba(255,255,255,0.5); + font-size: 30px; +} + +.button { + display: inline-block; + width: 200px; + height: 50px; + line-height: 50px; + text-align: center; + background-color: #eb1e64; + border-radius: 5px; + cursor: pointer; + margin: 0 10px; +} + +.button:hover { + opacity: 0.9; +} + +.wrapper iframe { + margin-top: 40px; + border: 2px solid #111; +} diff --git a/app/modules/Bin/actions/addFile.js b/app/modules/Bin/actions/addFile.js index 030e532..5e94e9e 100644 --- a/app/modules/Bin/actions/addFile.js +++ b/app/modules/Bin/actions/addFile.js @@ -5,15 +5,19 @@ function addFile({state}) { name: fileName, content: '' }); + if (ext === 'ts' && !state.get('bin.currentBin.loaders.typescript')) { state.set('bin.currentBin.loaders.typescript', {}); } + if (ext === 'css' && !state.get('bin.currentBin.loaders.css')) { state.set('bin.currentBin.loaders.css', {}); } + if (ext === 'coffee' && !state.get('bin.currentBin.loaders.coffeescript')) { state.set('bin.currentBin.loaders.coffeescript', {}); } + if (ext === 'less' && !state.get('bin.currentBin.loaders.css.less')) { if (!state.get('bin.currentBin.loaders.css')) { state.set('bin.currentBin.loaders.css', {}); @@ -22,6 +26,7 @@ function addFile({state}) { less: true }); } + if (ext === 'scss' && !state.get('bin.currentBin.loaders.css.sass')) { if (!state.get('bin.currentBin.loaders.css')) { state.set('bin.currentBin.loaders.css', {}); @@ -30,6 +35,14 @@ function addFile({state}) { sass: true }); } + + if ((ext === 'html' || ext === 'txt') && !state.get('bin.currentBin.loaders.raw')) { + state.set('bin.currentBin.loaders.raw', {}); + } + + if (ext === 'json' && !state.get('bin.currentBin.loaders.json')) { + state.set('bin.currentBin.loaders.json', {}); + } } export default addFile; diff --git a/app/modules/Bin/actions/setDefaultBin.js b/app/modules/Bin/actions/setDefaultBin.js deleted file mode 100644 index 4bcf0c6..0000000 --- a/app/modules/Bin/actions/setDefaultBin.js +++ /dev/null @@ -1,23 +0,0 @@ -function setDefaultBin({state}) { - state.set('bin.currentBin', { - packages: {}, - loaders: {}, - files: [{ - name: 'main.js', - content: [ - '/*', - ' Welcome to WebpackBin', - '', - ' - Add packages from NPM', - ' - Add loaders for modern JavaScript, Css, Typescript, Coffeescript, Sass, Less etc.', - ' - Use boilerplates to quickly load up packages with Hello World examples', - ' - Use bin.log() in your code to log values', - ' - There is a DOM element with ID "app" which you can render to', - '*/' - ].join('\n') - }] - }); - state.set('bin.selectedFileIndex', 0); -} - -export default setDefaultBin; diff --git a/app/modules/Bin/actions/setEmptyBin.js b/app/modules/Bin/actions/setEmptyBin.js new file mode 100644 index 0000000..ec1bd32 --- /dev/null +++ b/app/modules/Bin/actions/setEmptyBin.js @@ -0,0 +1,13 @@ +function setEmptyBin({state}) { + state.set('bin.currentBin', { + packages: {}, + loaders: {}, + files: [{ + name: 'main.js', + content: '' + }] + }); + state.set('bin.selectedFileIndex', 0); +} + +export default setEmptyBin; diff --git a/app/modules/Bin/actions/setWelcomeBin.js b/app/modules/Bin/actions/setWelcomeBin.js new file mode 100644 index 0000000..a353864 --- /dev/null +++ b/app/modules/Bin/actions/setWelcomeBin.js @@ -0,0 +1,65 @@ +function setWelcomeBin({state}) { + state.set('bin.currentBin', { + packages: {}, + loaders: { + css: {}, + raw: {} + }, + files: [{ + name: 'main.js', + content: [ + '// Just require css files to load them', + 'require(\'./styles.css\');', + '', + '// You can load HTML files and many other type of files', + 'var content = require(\'./welcome.html\');', + '', + '// Put your content in to the APP container', + 'document.querySelector(\'#app\').innerHTML = content;' + ].join('\n') + }, { + name: 'welcome.html', + content: [ + '
', + '

Welcome to WebpackBin!

', + '
    ', + '
  • Use bin.log() in your code to log values
  • ', + '
  • Use the DOM element with the ID app to render content
  • ', + '
  • Load CSS by just requiring it in your code, like this welcome page does
  • ', + '
  • Configure NPM packages and play around with libraries
  • ', + '
  • Add Webpack loaders for modern JavaScript, Css modules, Typescript, Coffeescript, Sass, Less, Html, Json etc.
  • ', + '
  • Check out the boilerplates to quickly load up some "Hello world" examples
  • ', + '
  • Check out the introduction video under "Info"
  • ', + '
', + '
' + ].join('\n') + }, { + name: 'styles.css', + content: [ + 'body {', + ' margin: 0;', + ' padding: 20px;', + ' font-family: Consolas, Arial;', + ' color: #333;', + ' background-color: #FAFAFA;', + '}', + '', + 'h1 {', + ' text-align: center;', + ' font-size: 46px;', + '}', + '', + 'ul {', + ' font-size: 20px;', + '}', + '', + 'li {', + ' margin-bottom: 10px;', + '}' + ].join('\n') + }] + }); + state.set('bin.selectedFileIndex', 0); +} + +export default setWelcomeBin; diff --git a/app/modules/Bin/actions/shouldSave.js b/app/modules/Bin/actions/shouldSave.js deleted file mode 100644 index 8f58187..0000000 --- a/app/modules/Bin/actions/shouldSave.js +++ /dev/null @@ -1,17 +0,0 @@ -function shouldSave({input, state, output}) { - const bin = state.select('bin'); - if ( - bin.get('shouldLint') && - bin.get('hasTriedToRun') && - bin.get('isValid') && - bin.get('selectedFileIndex') === bin.get('lastLintedFileIndex') - ) { - output.true(); - } else { - output.false(); - } -} - -shouldSave.outputs = ['true', 'false']; - -export default shouldSave; diff --git a/app/modules/Bin/actions/toggleLoader.js b/app/modules/Bin/actions/toggleLoader.js index baebf31..f287db3 100644 --- a/app/modules/Bin/actions/toggleLoader.js +++ b/app/modules/Bin/actions/toggleLoader.js @@ -10,7 +10,9 @@ const loaderTemplates = { typescript: {}, coffeescript: {}, less: {}, - sass: {} + sass: {}, + raw: {}, + json: {} }; function toggleLoader({input, state}) { diff --git a/app/modules/Bin/signals/runClicked.js b/app/modules/Bin/factories/runBin.js similarity index 96% rename from app/modules/Bin/signals/runClicked.js rename to app/modules/Bin/factories/runBin.js index a3477cf..e882525 100644 --- a/app/modules/Bin/signals/runClicked.js +++ b/app/modules/Bin/factories/runBin.js @@ -3,7 +3,7 @@ import copy from 'cerebral-addons/copy'; import when from 'cerebral-addons/when'; import postCode from '../actions/postCode'; import httpGet from 'cerebral-module-http/get'; -import showSnackbar from '../factories/showSnackbar'; +import showSnackbar from './showSnackbar'; import hideSnackbar from '../actions/hideSnackbar'; import redirectToBin from '../actions/redirectToBin'; import gotNewBin from '../actions/gotNewBin'; diff --git a/app/modules/Bin/index.js b/app/modules/Bin/index.js index 7b44a78..5ccc0d3 100755 --- a/app/modules/Bin/index.js +++ b/app/modules/Bin/index.js @@ -1,5 +1,4 @@ import preventIfLive from '../Live/factories/preventIfLive'; -import runClicked from './signals/runClicked'; import fileClicked from './signals/fileClicked'; import codeChanged from './signals/codeChanged'; import rootRouted from './signals/rootRouted'; @@ -30,11 +29,14 @@ import loadingTimeoutReached from './signals/loadingTimeoutReached'; import hideSnackbar from './actions/hideSnackbar.js'; import logValueToggled from './signals/logValueToggled'; import logPathSelected from './signals/logPathSelected'; +import welcomeBinClicked from './signals/welcomeBinClicked'; +import emptyBinClicked from './signals/emptyBinClicked'; export default (options = {}) => { return (module, controller) => { module.addState({ + showWelcome: true, snackbar: { text: '', show: false @@ -51,7 +53,6 @@ export default (options = {}) => { selectedLogPath: [], shouldCheckLog: false, - hasTriedToRun: false, shouldLint: true, isLinting: false, lastLintedFileIndex: 0, @@ -74,7 +75,7 @@ export default (options = {}) => { module.addSignals({ snackbarTimedOut: preventIfLive([hideSnackbar]), codeChanged: preventIfLive(codeChanged), - runClicked: preventIfLive(runClicked), + runClicked: preventIfLive(saveShortcutPressed), fileClicked: preventIfLive(fileClicked), saveShortcutPressed: preventIfLive(saveShortcutPressed), addFileClicked: preventIfLive(addFileClicked), @@ -103,6 +104,8 @@ export default (options = {}) => { addFileAborted, addFileNameUpdated, addFileSubmitted, + welcomeBinClicked, + emptyBinClicked }); }; diff --git a/app/modules/Bin/signals/boilerplateClicked.js b/app/modules/Bin/signals/boilerplateClicked.js index 289f3e7..2817692 100644 --- a/app/modules/Bin/signals/boilerplateClicked.js +++ b/app/modules/Bin/signals/boilerplateClicked.js @@ -1,7 +1,7 @@ import set from 'cerebral-addons/set'; import when from 'cerebral-addons/when'; import hidePopups from '../factories/hidePopups'; -import runClicked from './runClicked'; +import runBin from '../factories/runBin'; import getBoilerplate from '../actions/getBoilerplate'; import setBoilerplate from '../actions/setBoilerplate'; import showSnackbar from '../factories/showSnackbar'; @@ -16,7 +16,7 @@ export default [ setBoilerplate, canControlBin, { true: [ - ...runClicked + ...runBin ], false: [ set('state:/bin.isRunning', true) diff --git a/app/modules/Bin/signals/emptyBinClicked.js b/app/modules/Bin/signals/emptyBinClicked.js new file mode 100644 index 0000000..33f27d1 --- /dev/null +++ b/app/modules/Bin/signals/emptyBinClicked.js @@ -0,0 +1,7 @@ +import setEmptyBin from '../actions/setEmptyBin'; +import set from 'cerebral-addons/set'; + +export default [ + setEmptyBin, + set('state:/bin.showWelcome', false) +] diff --git a/app/modules/Bin/signals/fileClicked.js b/app/modules/Bin/signals/fileClicked.js index 10e9b30..66a7275 100644 --- a/app/modules/Bin/signals/fileClicked.js +++ b/app/modules/Bin/signals/fileClicked.js @@ -4,6 +4,5 @@ import set from 'cerebral-addons/set'; export default [ copy('input:/index', 'state:/bin.selectedFileIndex'), - set('state:/bin.shouldSave', false), shouldLint ]; diff --git a/app/modules/Bin/signals/linted.js b/app/modules/Bin/signals/linted.js index 4e98eae..6c312d1 100644 --- a/app/modules/Bin/signals/linted.js +++ b/app/modules/Bin/signals/linted.js @@ -1,18 +1,9 @@ import set from 'cerebral-addons/set'; import copy from 'cerebral-addons/copy'; -import runClicked from './runClicked'; -import shouldSave from '../actions/shouldSave'; +import runBin from '../factories/runBin'; export default [ set('state:/bin.isLinting', false), copy('input:/isValid', 'state:/bin.isValid'), - shouldSave, { - true: [ - ...runClicked - ], - false: [ - set('state:/bin.hasTriedToRun', false) - ] - }, copy('state:/bin.selectedFileIndex', 'state:/bin.lastLintedFileIndex') ] diff --git a/app/modules/Bin/signals/opened.js b/app/modules/Bin/signals/opened.js index 3b919d5..073b7f1 100644 --- a/app/modules/Bin/signals/opened.js +++ b/app/modules/Bin/signals/opened.js @@ -5,7 +5,7 @@ import httpGet from 'cerebral-module-http/get'; import showSnackbar from '../factories/showSnackbar'; import hideSnackbar from '../actions/hideSnackbar'; import isNewBin from '../actions/isNewBin'; -import runClicked from './runClicked'; +import runBin from '../factories/runBin'; import connectToLiveBin from '../actions/connectToLiveBin'; export default [ @@ -30,13 +30,13 @@ export default [ connectToLiveBin ], isFalse: [ - ...runClicked + ...runBin ] } ], false: [ set('state:/bin.selectedFileIndex', 0), - ...runClicked + ...runBin ] } ]; diff --git a/app/modules/Bin/signals/rootRouted.js b/app/modules/Bin/signals/rootRouted.js index 0be3284..bc2d5fb 100644 --- a/app/modules/Bin/signals/rootRouted.js +++ b/app/modules/Bin/signals/rootRouted.js @@ -1,5 +1,5 @@ -import setDefaultBin from '../actions/setDefaultBin'; +import set from 'cerebral-addons/set'; export default [ - setDefaultBin + set('state:/bin.showWelcome', true) ]; diff --git a/app/modules/Bin/signals/saveShortcutPressed.js b/app/modules/Bin/signals/saveShortcutPressed.js index a98a0d1..7cad29e 100644 --- a/app/modules/Bin/signals/saveShortcutPressed.js +++ b/app/modules/Bin/signals/saveShortcutPressed.js @@ -1,6 +1,6 @@ import when from 'cerebral-addons/when'; import set from 'cerebral-addons/set'; -import runClicked from './runClicked'; +import runBin from '../factories/runBin'; import hasValidLinting from '../actions/hasValidLinting'; import canControlBin from '../../Live/actions/canControlBin'; @@ -10,20 +10,12 @@ export default [ isFalse: [ set('state:/bin.logs', []), set('state:/bin.selectedLogPath', []), - canControlBin, { - true: [ - hasValidLinting, { - true: [ - ...runClicked - ], - false: [ - set('state:/bin.hasTriedToRun', true) - ] - } - - ], - false: [ + when('state:/live.hasJoined'), { + isTrue: [ set('state:/bin.isRunning', true) + ], + isFalse: [ + ...runBin ] } ] diff --git a/app/modules/Bin/signals/welcomeBinClicked.js b/app/modules/Bin/signals/welcomeBinClicked.js new file mode 100644 index 0000000..b1cfccc --- /dev/null +++ b/app/modules/Bin/signals/welcomeBinClicked.js @@ -0,0 +1,9 @@ +import setWelcomeBin from '../actions/setWelcomeBin'; +import set from 'cerebral-addons/set'; +import runBin from '../factories/runBin'; + +export default [ + setWelcomeBin, + set('state:/bin.showWelcome', false), + ...runBin +] diff --git a/app/modules/Live/signals/liveToggled.js b/app/modules/Live/signals/liveToggled.js index d3f3f29..3ecb90f 100644 --- a/app/modules/Live/signals/liveToggled.js +++ b/app/modules/Live/signals/liveToggled.js @@ -1,7 +1,7 @@ import when from 'cerebral-addons/when'; import set from 'cerebral-addons/set'; import showSnackbar from '../../Bin/factories/showSnackbar'; -import runClicked from '../../Bin/signals/runClicked'; +import runBin from '../../Bin/factories/runBin'; import connect from '../actions/connect'; import disconnect from '../actions/disconnect'; @@ -14,7 +14,7 @@ export default [ isFalse: [ set('state:/bin.currentBin.isLive', true), showSnackbar('Anyone entering your bin will see live code updates!', true), - ...runClicked, + ...runBin, connect, { success: [ set('state:/live.connected', true) diff --git a/index.html b/index.html index 2699f1c..7bc4f3c 100755 --- a/index.html +++ b/index.html @@ -8,16 +8,49 @@
+
+ +

Loading it up!

+
diff --git a/package.json b/package.json index 52172be..f311fa4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cerebral-boilerplate", - "version": "0.6.0", + "version": "0.7.0", "description": "A boilerplate for Cerebral", "main": "server.js", "repository": { @@ -25,7 +25,7 @@ "client": "kotatsu serve app/main.js --public public --index index.html --config webpack.config.js --devtool eval-source-map --proxy /api http://www.webpackbin.dev:4000/api", "server": "kotatsu start start.js --source-maps", "build": "npm run build:client", - "build:client": "kotatsu build client app/main.js --config webpack.production.config.js --minify -o public/client_build.js", + "build:client": "kotatsu build client app/main.js --config webpack.production.config.js --source-maps --minify -o public/client_build.js", "build:server": "kotatsu build server start.js --minify -o server_build.js", "postinstall": "node configure && npm run build" }, @@ -57,8 +57,10 @@ "esprima-fb": "^15001.1001.0-dev-harmony-fb", "express": "^4.13.4", "gridfs-stream": "^1.1.1", + "htmlhint": "^0.9.12", "imports-loader": "^0.6.5", "jshint": "^2.9.1", + "json-loader": "^0.5.4", "kotatsu": "https://github.com/christianalfoni/kotatsu.git", "less": "^2.6.0", "less-loader": "^2.2.2", @@ -72,6 +74,7 @@ "nopt": "^3.0.6", "npm-extractor": "git+https://github.com/christianalfoni/npm-extractor.git", "process": "^0.11.2", + "raw-loader": "^0.5.1", "rc": "^1.1.6", "react": "^0.14.7", "react-dom": "^0.14.7", diff --git a/server/createLoaders.js b/server/createLoaders.js index f344117..d0cafb6 100644 --- a/server/createLoaders.js +++ b/server/createLoaders.js @@ -78,5 +78,23 @@ module.exports = function (currentLoaders) { loaders.push(loader); } + // HTML + if (currentLoaders.raw) { + var loader = { + test: /\.html?$/, + loader: 'raw' + } + loaders.push(loader); + } + + // JSON + if (currentLoaders.json) { + var loader = { + test: /\.json?$/, + loader: 'json' + } + loaders.push(loader); + } + return loaders; }; diff --git a/server/index.js b/server/index.js index 2abf024..f51731f 100755 --- a/server/index.js +++ b/server/index.js @@ -44,7 +44,9 @@ preLoadPackages([ 'less-loader', 'node-sass', 'sass-loader', - 'babel-plugin-transform-decorators-legacy' + 'babel-plugin-transform-decorators-legacy', + 'raw-loader', + 'json-loader' ]); // Init diff --git a/server/live.js b/server/live.js index a452106..94104e4 100644 --- a/server/live.js +++ b/server/live.js @@ -94,6 +94,10 @@ module.exports = function connection(client) { var sessionId = cookie.parse(client.upgradeReq.headers.cookie).webpackbin; var session = sessions.get(sessionId); + if (!session || !session.currentBin) { + return client.close(); + } + if (session.currentBin.isOwner) { channels[session.currentBin.id] = { controller: client,