0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-23 18:43:02 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
Scott Tolksdorf
22a480871b 'Sheet 2016-05-16 22:42:22 -04:00
Scott Tolksdorf
daa3b096b3 'Adding 2016-05-16 22:20:35 -04:00
Scott Tolksdorf
bfcf6ca7f2 Getting ready to switch to jsx 2016-05-16 22:03:20 -04:00
Scott Tolksdorf
15ffb138eb Editor working 2016-05-16 21:58:40 -04:00
Scott Tolksdorf
7321cc81ec It livesssssssss 2016-05-16 21:51:48 -04:00
Scott Tolksdorf
ba6ba0e51f tpk server is working 2016-05-16 21:38:37 -04:00
25 changed files with 901 additions and 1 deletions

View File

@@ -0,0 +1,21 @@
var React = require('react');
var _ = require('lodash');
var Nav = require('naturalcrit/nav/nav.jsx');
var Navbar = React.createClass({
render : function(){
return <Nav.base>
<Nav.section>
<Nav.logo />
<Nav.item href='/tpk' className='tpkLogo'>
<div>Total Player Knolling</div>
</Nav.item>
<Nav.item>v0.0.0</Nav.item>
</Nav.section>
{this.props.children}
</Nav.base>
}
});
module.exports = Navbar;

View File

@@ -0,0 +1,16 @@
.tpk nav{
.tpkLogo{
.animate(color);
font-family : CodeBold;
font-size : 12px;
color : white;
div{
margin-top : 2px;
margin-bottom : -2px;
}
&:hover{
color : @teal;
}
}
}

View File

@@ -0,0 +1,49 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var CodeEditor = require('naturalcrit/codeEditor/codeEditor.jsx');
var SheetEditor = React.createClass({
getDefaultProps: function() {
return {
value : "",
onChange : function(){}
};
},
cursorPosition : {
line : 0,
ch : 0
},
componentDidMount: function() {
var paneHeight = this.refs.main.parentNode.clientHeight;
this.refs.codeEditor.codeMirror.setSize(null, paneHeight);
},
handleTextChange : function(text){
this.props.onChange(text);
},
handleCursorActivty : function(curpos){
this.cursorPosition = curpos;
},
//Called when there are changes to the editor's dimensions
update : function(){
this.refs.codeEditor.updateSize();
},
render : function(){
return <div className='sheetEditor' ref='main'>
<CodeEditor
ref='codeEditor'
wrap={true}
language='jsx'
value={this.props.value}
onChange={this.handleTextChange}
onCursorActivity={this.handleCursorActivty} />
</div>
}
});
module.exports = SheetEditor;

View File

@@ -0,0 +1,3 @@
.COM{
}

View File

@@ -0,0 +1,54 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var utils = require('../utils');
var Box = React.createClass({
mixins : [utils],
getDefaultProps: function() {
return {
//name : 'box',
defaultData : {},
id : '',
title : '',
label : '',
shadow : false,
border : false
};
},
handleChange : function(newData){
this.updateData(newData);
},
renderChildren : function(){
return React.Children.map(this.props.children, (child)=>{
if(!React.isValidElement(child)) return null;
return React.cloneElement(child, {
onChange : this.handleChange,
data : this.data()
})
})
},
renderTitle : function(){
if(this.props.title) return <h5 className='title'>{this.props.title}</h5>
},
renderLabel : function(){
if(this.props.label) return <h5 className='label'>{this.props.label}</h5>
},
render : function(){
return <div className={cx('box', this.props.className, {
shadow : this.props.shadow,
border : this.props.border
})}>
{this.renderTitle()}
{this.renderChildren()}
{this.renderLabel()}
</div>
}
});
module.exports = Box;

View File

@@ -0,0 +1,30 @@
.box{
position : relative;
padding : 10px;
margin: 10px;
&.border{
border: 1px solid black;
}
&.shadow{
background-color: #ddd;
}
h5{
text-transform: uppercase;
font-size : 0.6em;
text-align: center;
width : 100%;
font-weight: 800;
&.title{
margin-top: -5px;
margin-bottom: 10px;
}
&.label{
margin-bottom: -5px;
margin-top: 10px;
}
}
}

View File

@@ -0,0 +1,13 @@
module.exports = {
TextInput : require('./textInput/textInput.jsx'),
PlayerInfo : require('./playerInfo/playerInfo.jsx'),
SkillList : require('./skillList/skillList.jsx'),
Skill : require('./skill/skill.jsx'),
//ShadowBox : require('./shadowBox/shadowBox.jsx'),
//BorderBox : require('./borderBox/borderBox.jsx'),
Box : require('./box/box.jsx')
}

View File

@@ -0,0 +1,25 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var TextInput = require('../textInput/textInput.jsx');
var Box = require('../box/box.jsx');
var PlayerInfo = React.createClass({
getDefaultProps: function() {
return {
title: "player info",
border : true
};
},
render : function(){
return <Box className='playerInfo' {...this.props} >
<TextInput label="Name" placeholder="name" />
<TextInput label="Class" />
<TextInput label="Race" />
{this.props.children}
</Box>
}
});
module.exports = PlayerInfo;

View File

@@ -0,0 +1,3 @@
.playerInfo{
margin-bottom: 20px;
}

View File

@@ -0,0 +1,62 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var utils = require('../utils');
var Skill = React.createClass({
getDefaultProps: function() {
return {
name : 'skill',
defaultData : {
prof : false,
expert : false,
val : ''
},
id : '',
label : '',
sublabel : '',
showExpert : false
};
},
id : utils.id,
data : utils.data,
updateData : utils.updateData,
handleToggleProf : function(){
this.updateData({
prof : !this.data().prof
})
},
handleToggleExpert : function(){
this.updateData({
expert : !this.data().expert
})
},
handleValChange : function(e){
console.log('yo');
this.updateData({
val : e.target.value
})
},
renderExpert : function(){
if(this.props.showExpert){
return <input type="radio" className='expertToggle' onChange={this.handleToggleExpert} checked={this.data().expert} />
}
},
render : function(){
return <div className='skill'>
{this.renderExpert()}
<input type="radio" className='skillToggle' onChange={this.handleToggleProf} checked={this.data().prof} />
<input type='text' onChange={this.handleValChange} value={this.data().val} />
<label>
{this.props.label}
<small>{this.props.sublabel}</small>
</label>
</div>
}
});
module.exports = Skill;

View File

@@ -0,0 +1,35 @@
.skill{
position : relative;
padding-left : 15px;
input[type="radio"]{
margin : 0px;
}
.expertToggle{
position : absolute;
top : 1px;
left : 0px;
}
input[type="text"]{
width : 25px;
margin-left : 10px;
background-color : transparent;
text-align : center;
border : none;
border-bottom : 1px solid black;
outline : none;
&:focus{
background-color : #ddd;
}
}
label{
margin-left : 10px;
font-size : 0.8em;
small{
margin-left : 5px;
font-size : 0.8em;
color : #999;
text-transform : uppercase;
}
}
}

View File

@@ -0,0 +1,61 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Skill = require('../skill/skill.jsx');
var Box = require('../box/box.jsx');
var skill_list = [
{name : 'Acrobatics', stat : 'Dex'},
{name : 'Animal Handling', stat : 'Wis'},
{name : 'Arcana', stat : 'Int'},
{name : 'Athletics', stat : 'Str'},
{name : 'Deception', stat : 'Cha'},
{name : 'History', stat : 'Int'},
{name : 'Insight', stat : 'Wis'},
{name : 'Intimidation', stat : 'Cha'},
{name : 'Investigation', stat : 'Int'},
{name : 'Medicine', stat : 'Wis'},
{name : 'Nature', stat : 'Int'},
{name : 'Perception', stat : 'Wis'},
{name : 'Performance', stat : 'Cha'},
{name : 'Persuasion', stat : 'Cha'},
{name : 'Religion', stat : 'Int'},
{name : 'Sleight of Hand', stat : 'Dex'},
{name : 'Stealth', stat : 'Dex'},
{name : 'Survival', stat : 'Wis'}
]
var SkillList = React.createClass({
getDefaultProps: function() {
return {
name : 'skills',
//title : 'Skills',
shadow : true,
border : false,
showExpert : false
};
},
renderSkills : function(){
return _.map(skill_list, (skill)=>{
return <Skill
label={skill.name}
sublabel={'(' + skill.stat + ')'}
showExpert={this.props.showExpert} />
})
},
render : function(){
return <Box className='skillList' {...this.props}>
{this.renderSkills()}
{this.props.children}
</Box>
}
});
module.exports = SkillList;

View File

@@ -0,0 +1,3 @@
.COM{
}

View File

@@ -0,0 +1,44 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var utils = require('../utils');
var TextInput = React.createClass({
getDefaultProps: function() {
return {
name : 'text',
defaultData : '',
id : '',
label : '',
};
},
id : utils.id,
data : utils.data,
updateData : utils.updateData,
handleChange : function(e){
this.updateData(e.target.value);
},
renderLabel : function(){
if(this.props.label) return <label htmlFor={this.id()}>{this.props.label}</label>
},
render : function(){
return <div className='textInput'>
{this.renderLabel()}
<input
id={this.id()}
type='text'
onChange={this.handleChange}
value={this.data()}
placeholder={this.props.placeholder}
/>
</div>
}
});
module.exports = TextInput;

View File

@@ -0,0 +1,6 @@
.textInput{
label{
display: inline-block;
width : 50px;
}
}

View File

@@ -0,0 +1,33 @@
var _ = require('lodash');
module.exports = {
id : function(){
if(this.props.id) return this.props.id;
if(this.props.label) return _.snakeCase(this.props.label);
if(this.props.title) return _.snakeCase(this.props.title);
return this.props.name;
},
data : function(){
if(!this.id()) return this.props.data || this.props.defaultData;
if(this.props.data && this.props.data[this.id()]) return this.props.data[this.id()];
return this.props.defaultData;
},
updateData : function(val){
if(typeof this.props.onChange !== 'function') throw "No onChange handler set";
var newVal = val;
//Clone the data if it's an object to avoid mutation bugs
if(_.isObject(val)) newVal = _.extend({}, this.data(), val);
if(this.id()){
this.props.onChange({
[this.id()] : newVal
});
}else{
//If the box has no id, don't add it to the chain
this.props.onChange(newVal)
}
}
}

View File

@@ -0,0 +1,79 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var jsx2json = require('naturalcrit/jsx-parser');
var Parts = require('./parts');
var SheetRenderer = React.createClass({
getDefaultProps: function() {
return {
code : '',
characterData : {},
onChange : function(){},
};
},
renderElement : function(node, key){
if(!node.tag) return null;
if(!Parts[node.tag]) throw 'Could Not Find Element: ' + node.tag
return React.createElement(
Parts[node.tag],
{key : key, ...node.props},
...this.renderChildren(node.children))
},
renderChildren : function(nodes){
return _.map(nodes, (node, index)=>{
if(_.isString(node)) return node;
return this.renderElement(node, index);
})
},
renderSheet : function(){
try{
var nodes = jsx2json(this.props.code);
nodes = _.map(nodes, (node)=>{
node.props.data = this.props.characterData;
node.props.onChange = (newData)=>{
this.props.onChange(_.extend(this.props.characterData, newData));
}
return node
})
return this.renderChildren(nodes);
}catch(e){
return <div>Error bruh {e.toString()}</div>
}
},
render : function(){
return <div className='sheetRenderer'>
<h2>Character Sheet</h2>
<div className='sheetContainer' ref='sheetContainer'>
{this.renderSheet()}
</div>
</div>
}
});
module.exports = SheetRenderer;
/*
<Temp text="cool">yo test <a href="google.com">link</a> </Temp>
*/

View File

@@ -0,0 +1,11 @@
.sheetRenderer{
padding-right : 10px;
.sheetContainer{
background-color: white;
padding : 20px;
}
}

78
client/tpk/tpk.jsx Normal file
View File

@@ -0,0 +1,78 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Nav = require('naturalcrit/nav/nav.jsx');
var Navbar = require('./navbar/navbar.jsx');
var SplitPane = require('naturalcrit/splitPane/splitPane.jsx');
var SheetEditor = require('./sheetEditor/sheetEditor.jsx');
var SheetRenderer = require('./sheetRenderer/sheetRenderer.jsx');
const SPLATSHEET_TEMPLATE = 'splatsheet_template';
const SPLATSHEET_DATA = 'splatsheet_data';
var TPK = React.createClass({
getInitialState: function() {
return {
sheetCode: "<Box>\n\t<TextInput label='test' />\n</Box>",
sheetData : {}
};
},
//remove later
componentDidMount: function() {
this.setState({
sheetCode : localStorage.getItem(SPLATSHEET_TEMPLATE) || this.state.sheetCode,
sheetData : JSON.parse(localStorage.getItem(SPLATSHEET_DATA)) || this.state.sheetData
})
},
handleSplitMove : function(){
this.refs.editor.update();
},
handleCodeChange : function(code){
this.setState({
sheetCode : code
});
localStorage.setItem(SPLATSHEET_TEMPLATE, code);
},
handleDataChange : function(data){
this.setState({
sheetData : JSON.parse(JSON.stringify(data)),
});
localStorage.setItem(SPLATSHEET_DATA, JSON.stringify(data));
},
render : function(){
return <div className='tpk page'>
<Navbar>
<Nav.section>
<Nav.item>
yo dawg
</Nav.item>
</Nav.section>
</Navbar>
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
<SheetEditor value={this.state.sheetCode} onChange={this.handleCodeChange} ref='editor' />
<SheetRenderer
code={this.state.sheetCode}
characterData={this.state.sheetData}
onChange={this.handleDataChange} />
</SplitPane>
</div>
</div>
}
});
module.exports = TPK;

19
client/tpk/tpk.less Normal file
View File

@@ -0,0 +1,19 @@
@import 'naturalcrit/styles/core.less';
.tpk{
height : 100%;
background-color : @steel;
.page{
display : flex;
height : 100%;
flex-direction : column;
.content{
position : relative;
//height : calc(~"100% - 29px"); //Navbar height
height : 100%;
flex : auto;
}
}
}

View File

@@ -8,6 +8,7 @@ var gulp = vitreumTasks(gulp, {
entryPoints: [
'./client/main',
'./client/homebrew',
'./client/tpk',
'./client/admin'
],
@@ -29,6 +30,7 @@ var gulp = vitreumTasks(gulp, {
"codemirror",
"codemirror/mode/gfm/gfm.js",
'codemirror/mode/javascript/javascript.js',
'codemirror/mode/jsx/jsx.js',
"moment",
"superagent",

View File

@@ -45,6 +45,8 @@ app.get('/admin', function(req, res){
app = require('./server/homebrew.api.js')(app);
app = require('./server/homebrew.server.js')(app);
//Populate TPK routes
app = require('./server/tpk.server.js')(app);
app.get('*', function (req, res) {

22
server/tpk.server.js Normal file
View File

@@ -0,0 +1,22 @@
var _ = require('lodash');
var vitreumRender = require('vitreum/render');
module.exports = function(app){
//Edit Page
app.get('/tpk*', function(req, res){
vitreumRender({
page: './build/tpk/bundle.dot',
globals:{},
prerenderWith : './client/tpk/tpk.jsx',
initialProps: {
url: req.originalUrl,
},
clearRequireCache : !process.env.PRODUCTION,
}, function (err, page) {
return res.send(page)
});
});
return app;
};

View File

@@ -10,6 +10,7 @@ if(typeof navigator !== 'undefined'){
//Language Modes
require('codemirror/mode/gfm/gfm.js'); //Github flavoured markdown
require('codemirror/mode/javascript/javascript.js');
require('codemirror/mode/jsx/jsx.js');
}
@@ -38,7 +39,7 @@ var CodeEditor = React.createClass({
},
componentWillReceiveProps: function(nextProps){
if(this.codeMirror && nextProps.value !== undefined && this.codeMirror.getValue() != nextProps.value) {
if(this.codeMirror && nextProps.value && this.codeMirror.getValue() != nextProps.value) {
this.codeMirror.setValue(nextProps.value);
}
},

View File

@@ -0,0 +1,228 @@
var WHITESPACE = /(\s|\t|\n|\r)/g;
var NUMBERS = /[0-9]/;
var LETTERS = /[a-zA-Z_]/;
var tokenizer = function(input){
var tokens = [];
var current = 0;
var inTag = false;
while(current < input.length){
var char = input[current];
var getToken = function(regex){
var value = '';
while(regex.test(char) && current < input.length){
value += char;
char = input[++current];
}
return value;
}
if(inTag){
if(char == '>'){
inTag = false;
tokens.push({
type : 'closeTag'
})
}
else if(char == '/' && input[current+1] == '>'){
inTag = false;
tokens.push({
type : 'endTag'
})
current++;
}
else if(char == '='){
tokens.push({
type : 'equals'
});
}
else if(WHITESPACE.test(char)){
}
else if(NUMBERS.test(char)){
tokens.push({
type : 'number',
value : getToken(NUMBERS)*1
});
current--;
}
else if(LETTERS.test(char)){
var word = getToken(LETTERS);
if(word == 'true' || word == 'false'){
tokens.push({
type : 'boolean',
value : word == 'true'
});
}else{
tokens.push({
type : 'word',
value : word
});
}
current--;
}
else if(char == "'"){
char = input[++current]
tokens.push({
type : 'text',
value : getToken(/[^\']/)
});
}
else if(char == '"'){
char = input[++current]
tokens.push({
type : 'text',
value : getToken(/[^\"]/)
});
}
}
//Not tokenizing a tag definition
else{
//End tag
if(char == '<' && input[current+1] == '/'){
char = input[++current]
char = input[++current]
tokens.push({
type : 'endTag',
value : getToken(LETTERS)
})
}
else if(char == '<'){
inTag = true;
char = input[++current];
tokens.push({
type : 'openTag',
value : getToken(LETTERS)
})
current--;
}
else{
//Handle slush text
var value = '';
while(char != '<' && current < input.length){
value += char;
char = input[++current];
}
value = value.trim()
if(value){
tokens.push({
type : 'text',
value : value
});
}
current--;
}
}
current++;
}
return tokens;
}
var parser = function(tokens){
var nodes = [];
var current = 0;
var token = tokens[current];
var parseProps = function(){
var props = {};
var key = null;
var last = null;
while(current < tokens.length && token.type != 'endTag' && token.type != 'closeTag'){
if(last && token.type == 'word'){
props[last] = true;
last = token.value;
}else if(!key && token.type == 'word'){
last = token.value;
}else if(last && token.type == 'equals'){
key = last;
last = null;
}else if(key && (token.type == 'number' || token.type == 'text' || token.type == 'boolean')){
props[key] = token.value;
key = null;
last = null;
}else{
throw "Invalid property value: " + key + '=' + token.value;
}
token = tokens[++current];
}
if(last) props[last] = true;
return props;
}
var genNode = function(tagType){
token = tokens[++current];
var node = {
tag : tagType,
props : parseProps(),
children : getChildren(tagType)
}
return node
}
var getChildren = function(tagType){
var children = [];
while(current < tokens.length){
if(token.type == 'endTag'){
if(token.value && token.value != tagType){
throw "Invalid closing tag: " + token.value + ". Expected closing tag of type: " + tagType
}else{
break;
}
}
if(token.type == 'openTag'){
children.push(genNode(token.value));
}else if(token.type == 'text'){
children.push(token.value);
}
token = tokens[++current];
}
return children;
}
return getChildren();
}
module.exports = function(input){
return parser(tokenizer(input));
}
/*
SOME TEST, remove later
var test1 = `
<div test="hey there champ" more_cool=false size=0>
<span>
Hey there!
<a>so fucking cool </a>
</span>
let's go party
<a href='neato' />
</div>
`
var test2 = "<div cool=0 same>Hey there!</div>"
var tokens = tokenizer(test1);
console.log(test1, '\n---\n', tokens, '---\n', JSON.stringify(parser(tokens), null, ' '));
*/