0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-24 22:53:00 +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
43 changed files with 867 additions and 520 deletions

View File

@@ -1,5 +1,5 @@
@import (less) 'naturalcrit/phbStyle/phb.style.less';
@import (less) './client/homebrew/phbStyle/phb.style.less';
.pane{
position : relative;
}

View File

Before

Width:  |  Height:  |  Size: 327 B

After

Width:  |  Height:  |  Size: 327 B

View File

Before

Width:  |  Height:  |  Size: 530 B

After

Width:  |  Height:  |  Size: 530 B

View File

@@ -1,7 +1,7 @@
@import (less) 'shared/naturalcrit/styles/reset.less';
@import (less) 'shared/naturalcrit/phbStyle/phb.fonts.css';
@import (less) 'shared/naturalcrit/phbStyle/phb.assets.less';
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
@import (less) './client/homebrew/phbStyle/phb.assets.less';
//Colors
@background : #EEE5CE;
@noteGreen : #e0e5c1;

View File

Before

Width:  |  Height:  |  Size: 864 B

After

Width:  |  Height:  |  Size: 864 B

View File

@@ -22,13 +22,13 @@ var Main = React.createClass({
beta : false
},
{
id : 'spellsort',
path : '/spellsort',
name : 'Spellsort',
id : 'homebrew2',
path : '/homebrew',
name : 'The Homebrewery',
icon : <HomebrewIcon />,
desc : 'Sort and search through spells',
desc : 'Make authentic-looking 5e homebrews using Markdown',
show : true,
show : false,
beta : true
},
{

View File

@@ -1,32 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Sorter = React.createClass({
getDefaultProps: function() {
return {
spells : []
};
},
renderSpell : function(spell){
return <div className='spell' key={spell.id}>
{spell.name}
</div>
},
renderSpells : function(){
return _.map(this.props.spells, (spell)=>{
return this.renderSpell(spell)
});
},
render : function(){
return <div className='sorter'>
{this.renderSpells()}
</div>
}
});
module.exports = Sorter;

View File

@@ -1,97 +0,0 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var Markdown = require('marked');
var SpellRenderer = React.createClass({
getDefaultProps: function() {
return {
spells : []
};
},
//TODO: Add in ritual tag
getSubtitle : function(spell){
if(spell.level == 0) return <p><em>{spell.school} cantrip</em></p>;
if(spell.level == 1) return <p><em>{spell.level}st-level {spell.school}</em></p>;
if(spell.level == 2) return <p><em>{spell.level}nd-level {spell.school}</em></p>;
if(spell.level == 3) return <p><em>{spell.level}rd-level {spell.school}</em></p>;
return <p><em>{spell.level}th-level {spell.school}</em></p>;
},
getComponents : function(spell){
var result = [];
if(spell.components.v) result.push('V');
if(spell.components.s) result.push('S');
if(spell.components.m) result.push('M ' + spell.components.m);
return result.join(', ');
},
getHigherLevels : function(spell){
if(!spell.scales) return null;
return <p>
<strong><em>At Higher Levels. </em></strong>
<span dangerouslySetInnerHTML={{__html: Markdown(spell.scales)}} />
</p>;
},
getClasses : function(spell){
if(!spell.classes || !spell.classes.length) return null;
var classes = _.map(spell.classes, (cls)=>{
return _.capitalize(cls);
}).join(', ');
return <li>
<strong>Classes:</strong> {classes}
</li>
},
renderSpell : function(spell){
console.log('rendering', spell);
return <div className='spell' key={spell.id}>
<h4>{spell.name}</h4>
{this.getSubtitle(spell)}
<hr />
<ul>
<li>
<strong>Casting Time:</strong> {spell.casting_time}
</li>
<li>
<strong>Range:</strong> {spell.range}
</li>
<li>
<strong>Components:</strong> {this.getComponents(spell)}
</li>
<li>
<strong>Duration:</strong> {spell.duration}
</li>
{this.getClasses(spell)}
</ul>
<span dangerouslySetInnerHTML={{__html: Markdown(spell.description)}} />
{this.getHigherLevels(spell)}
</div>
},
renderSpells : function(){
return _.map(this.props.spells, (spell)=>{
return this.renderSpell(spell);
})
},
render : function(){
return <div className='spellRenderer'>
<div className='phb'>
{this.renderSpells()}
</div>
</div>
}
});
module.exports = SpellRenderer;

View File

@@ -1,40 +0,0 @@
@import (less) 'naturalcrit/phbStyle/phb.style.less';
.pane{
position : relative;
}
.spellRenderer{
overflow-y : scroll;
&>.phb{
margin-right : auto;
margin-bottom : 30px;
margin-left : auto;
box-shadow : 1px 4px 14px #000;
height : auto;
width : 100%;
display: flex;
flex-direction: column;
flex-wrap: wrap;
column-count : initial;
column-fill : initial;
column-gap : initial;
column-width : initial;
-webkit-column-count : initial;
-moz-column-count : initial;
-webkit-column-width : initial;
-moz-column-width : initial;
-webkit-column-gap : initial;
-moz-column-gap : initial;
.spell{
display: inline;
width : 8cm;
}
}
}

View File

@@ -1,45 +0,0 @@
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 SpellRenderer = require('./spellRenderer/spellRenderer.jsx');
var Sorter = require('./sorter/sorter.jsx');
var SpellSort = React.createClass({
getDefaultProps: function() {
return {
spells : []
};
},
handleSplitMove : function(){
},
render : function(){
console.log(this.props.spells);
return <div className='spellsort page'>
<Navbar>
<Nav.section>
yo
</Nav.section>
</Navbar>
<div className='content'>
<SplitPane onDragFinish={this.handleSplitMove} ref='pane'>
<Sorter spells={this.props.spells} />
<SpellRenderer spells={this.props.spells} />
</SplitPane>
</div>
</div>
}
});
module.exports = SpellSort;

View File

@@ -1,4 +0,0 @@
@import 'naturalcrit/styles/core.less';
.spellsort{
}

View File

@@ -8,8 +8,8 @@ var Navbar = React.createClass({
return <Nav.base>
<Nav.section>
<Nav.logo />
<Nav.item href='/spellsort' className='spellsortLogo'>
<div>Spellsort</div>
<Nav.item href='/tpk' className='tpkLogo'>
<div>Total Player Knolling</div>
</Nav.item>
<Nav.item>v0.0.0</Nav.item>
</Nav.section>

View File

@@ -1,6 +1,6 @@
.spellsort nav{
.spellsortLogo{
.tpk nav{
.tpkLogo{
.animate(color);
font-family : CodeBold;
font-size : 12px;
@@ -13,4 +13,4 @@
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,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,7 +8,7 @@ var gulp = vitreumTasks(gulp, {
entryPoints: [
'./client/main',
'./client/homebrew',
'./client/spellsort',
'./client/tpk',
'./client/admin'
],
@@ -30,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",
@@ -44,7 +45,7 @@ var gulp = vitreumTasks(gulp, {
var rename = require('gulp-rename');
var less = require('gulp-less');
gulp.task('phb', function(){
gulp.src('./shared/naturalcrit/phbStyle/phb.style.less')
gulp.src('./client/homebrew/phbStyle/phb.style.less')
.pipe(less())
.pipe(rename('phb.standalone.css'))
.pipe(gulp.dest('./'));

View File

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

View File

@@ -1,164 +0,0 @@
module.exports = {
"Abi-Dalzim's Horrid Wilting": {
"athigherlevel": "",
"description": "(a bit of sponge)\r\nYou draw the moisture from every creature in a 30-foot cube centered on a point you choose within range. Each creature in that area must make a Constitution saving throw. Constructs and undead aren't affected, and plants and water elementals make this saving throw with disadvantage. A creature takes 10d8 necrotic damage on a failed save, or half as much damage on a successful one.",
"source": "Elemental Evil",
"level": "8",
"range": "150 feet",
"casting_time": "1 Action",
"id": "406",
"slug": "abi-dalzims-horrid-wilting",
"scag": "0",
"page": "15",
"components": "V, S, M",
"ritual": "No",
"name": "Abi-Dalzim's Horrid Wilting",
"school": "Necromancy",
"ee": "1",
"classes": [
"Sorcerer",
"Wizard"
],
"duration": "Instantaneous",
"concentration": "No"
},
"Absorb Elements ": {
"athigherlevel": " When you cast this spell using a spell slot of 2nd level or higher, the extra damage increases by 1d6 for each slot level above 1st.",
"description": "1 Reaction, which you take when you take acid, cold, fire, lightning, or thunder damage\n\nThe spell captures some of the incoming energy, lessening its effect on you and storing it for your next melee attack. You have resistance to the triggering damage type until the start of your next turn. Also, the first time you hit with a melee attack on your next turn, the target takes an extra 1d6 damage of the triggering type, and the spell ends.\r\n",
"source": "Elemental Evil",
"level": "1",
"range": "Self",
"casting_time": "Special",
"id": "377",
"slug": "absorb-elements",
"scag": "0",
"page": "15",
"components": "S",
"ritual": "No",
"name": "Absorb Elements ",
"school": "Abjuration",
"ee": "1",
"classes": [
"Druid",
"Ranger",
"Wizard"
],
"duration": "1 round",
"concentration": "No"
},
"Acid Splash": {
"athigherlevel": "This spell's damage increases by 1d6 when you reach 5th level (2d6), 11th level (3d6), and 17th level (4d6).",
"description": "You hurl a bubble of acid. \nChoose one creature within range, or choose two creatures within range that are within 5 feet of each other. A target must succeed on a Dexterity saving throw or take 1d6 acid damage. \n\n",
"source": "Player's Handbook",
"level": "0",
"range": "60 feet",
"casting_time": "1 Action",
"id": "1",
"slug": "acid-splash",
"scag": "0",
"page": "211",
"components": "V, S",
"ritual": "No",
"name": "Acid Splash",
"school": "Conjuration",
"ee": "0",
"classes": [
"Sorcerer",
"Wizard"
],
"duration": "Instantaneous",
"concentration": "No"
},
"Aganazzar's Scorcher": {
"athigherlevel": " When you cast this spell using a spell slot of 3rd level or higher, the damage increases by 1d8 for each slot level above 2nd.",
"description": "A line of roaring flame 30 feet long and 5 feet wide emanates from you in a direction you choose. Each creature in the line must make a Dexterity saving throw. A creature takes 3d8 fire damage on a failed save, or half as much damage on a successful one.\r\n",
"source": "Elemental Evil",
"level": "2",
"range": "30 feet",
"casting_time": "1 Action",
"id": "399",
"slug": "aganazzars-scorcher",
"scag": "0",
"page": "15",
"components": "V, S, M",
"ritual": "No",
"name": "Aganazzar's Scorcher",
"school": "Evocation",
"ee": "1",
"classes": [
"Sorcerer",
"Wizard"
],
"duration": "Instantaneous",
"concentration": "No"
},
"Aid": {
"athigherlevel": "When you cast this spell using a spell slot of 3rd level or higher, a target's hit points increase by an additional 5 for each slot level above 2nd.",
"description": "Your spell bolsters your allies with toughness and resolve. \nChoose up to three creatures within range. Each target's hit point maximum and current hit points increase by 5 for the duration.",
"source": "Player's Handbook",
"level": "2",
"range": "30 feet",
"casting_time": "1 Action",
"id": "2",
"slug": "aid",
"scag": "0",
"page": "211",
"components": "V, S, M (a tiny strip of white cloth)",
"ritual": "No",
"name": "Aid",
"school": "Abjuration",
"ee": "0",
"classes": [
"Cleric",
"Paladin"
],
"duration": "8 hours",
"concentration": "No"
},
"Alarm (Ritual)": {
"athigherlevel": "",
"description": "You set an alarm against unwanted intrusion. \r\nChoose a door, a window, or an area within range that is no larger than a 20-foot cube. Until the spell ends, an alarm alerts you whenever a tiny or larger creature touches or enters the warded area. When you cast the spell, you can designate creatures that won't set off the alarm. You also choose whether the alarm is mental or audible. \n\nA mental alarm alerts you with a ping in your mind if you are within 1 mile of the warded area. This ping awakens you if you are sleeping. \r\nAn audible alarm produces the sound of a hand bell for 10 seconds within 60 feet.",
"source": "Player's Handbook",
"level": "1",
"range": "30 feet",
"casting_time": "1 Minute",
"id": "3",
"slug": "alarm-ritual",
"scag": "0",
"page": "211",
"components": "V, S, M (a tiny bell and a piece of fine silver wire)",
"ritual": "Yes",
"name": "Alarm (Ritual)",
"school": "Abjuration",
"ee": "0",
"classes": [
"Ranger",
"Wizard"
],
"duration": "8 hours",
"concentration": "No"
},
"Alter Self": {
"athigherlevel": "",
"description": "You assume a different form.\r\nWhen you cast the spell, choose one of the following options, the effects of which last for the duration of the spell. While the spell lasts, you can end one option as an action to gain the benefits of a different one.\r\n\r\nAquatic Adaptation. You adapt your body to an aquatic environment, sprouting gills, and growing webbing between your fingers. You can breathe underwater and gain a swimming speed equal to your walking speed.\r\n\r\nChange Appearance. You transform your appearnce. You decide what you look like, including your height, weight, facial features, sound of your voice, hair length, coloration, and distinguishing characteristics, if any. \r\nYou can make yourself appear as a member of another race, though none of your statistics change. You also don't appear as a creature of a different size than you, and your basic shape stays the same; if you're bipedal, you can't use this spell to become quadrupedal, for instance. At any time for the duration of the spell, you can use your action to change your appearance in this way again.\r\n\n&nbsp;Natural Weapons. You grow claws, fangs, spines,&nbsp;horns, or a different natural weapon of your choice. Your unarmed strikes deal 1d6 bludgeoning, piercing, or slashing damage, as appropriate to the natural weapon you chose, and you are proficient with your unarmed strikes. Finally, the natural weapon is magic and you have a +1 bonus to the attack and damage rolls you make using it.",
"source": "Player's Handbook",
"level": "2",
"range": "Self",
"casting_time": "1 Action",
"id": "4",
"slug": "alter-self",
"scag": "0",
"page": "211",
"components": "V, S",
"ritual": "No",
"name": "Alter Self",
"school": "Transmutation",
"ee": "0",
"classes": [
"Sorcerer",
"Wizard"
],
"duration": "Concentration, up to 1 hour",
"concentration": "Yes"
},
}

View File

@@ -7,6 +7,15 @@ var HomebrewModel = require('./homebrew.model.js').model;
module.exports = function(app){
/*
app.get('/homebrew/new', function(req, res){
var newHomebrew = new HomebrewModel();
newHomebrew.save(function(err, obj){
return res.redirect('/homebrew/edit/' + obj.editId);
})
})
*/
//Edit Page
app.get('/homebrew/edit/:id', function(req, res){

View File

@@ -1,49 +0,0 @@
var _ = require('lodash');
var spells = require('./5espells.js');
String.prototype.replaceAll = function(s,r){return this.split(s).join(r)}
var parsedSpells = _.map(spells, (spell)=>{
var comp = {}
var name = spell.name.replace(' (Ritual)', '');
return {
id : _.snakeCase(name),
name : name,
description : spell.description
.replaceAll('\r\n', '\n')
.replaceAll('&nbsp;', ''),
scales : spell.athigherlevel,
components : {},
classes : _.map(spell.classes || [], (cls)=>{return cls.toLowerCase();}),
level : Number(spell.level),
ritual : spell.ritual == "Yes",
concentration : spell.concentration == "Yes",
range : spell.range,
duration : spell.duration,
school : spell.school.toLowerCase(),
source : spell.source,
page : spell.page,
}
});
module.exports = parsedSpells;

View File

@@ -1,59 +0,0 @@
var _ = require('lodash');
var spells = [
{
name : "Acid Splash",
casting_time : "1 action",
components : {v : true, s : true},
description : `You hurl a bubble of acid. Choose one creature within range, or choose two creatures within range that are within 5 feet of each other.
A target must succeed on a Dexterity saving throw or take 1d6 acid damage.`,
scales : 'This spells damage increases by 1d6 when you reach 5th level (2d6), 11th level (3d6), and 17th level (4d6).',
duration : "Instantaneous",
concentration : false,
level : 0,
ritual : false,
range : "60 feet",
school : "Conjuration",
classes : ["sorcerer", "wizard"],
source : "Player's Handbook",
page : "pg.211"
},
{
name : "Aid",
casting_time : "1 action",
components : {v : true, s : true, m : "(a tiny strip of white cloth)"},
description : `Your spell bolsters your allies with toughness and resolve. Choose up to three creatures within range. Each targets hit point maximum and current hit points increase by 5 for the duration. At Higher Levels. When you cast this spell using a spell slot of 3rd level or higher, a targets hit points increase by an additional 5 for each slot level above 2nd.`,
duration : "8 hours",
concentration : false,
level : 2,
ritual : false,
range : "30 feet",
school : "Abjuration",
classes : [],
source : "Player's Handbook",
page : "pg.211"
},
{
name : "Antimagic Field",
casting_time : "1 action",
components : {v : true, s : true, m : "(a pinch of powdered iron or iron filings)"},
description : `A 10-foot-radius invisible sphere of antimagic surrounds you. This area is divorced from the magical energy that suffuses the multiverse. Within the sphere, spells cant be cast, summoned creatures disappear, and even magic items become mundane. Until the spell ends, the sphere moves with you, centered on you. Spells and other magical effects, except those created by an artifact or a deity, are suppressed in the sphere and cant protrude into it. A slot expended to cast a suppressed spell is consumed. While an effect is suppressed, it doesnt function, but the time it spends suppressed counts against its duration. Targeted Effects. Spells and other magical effects, such as magic missile and charm person, that target a creature or an object in the sphere have no effect on that target. Areas of Magic. The area of another spell or magical effect, such as fireball, cant extend into the sphere. If the sphere overlaps an area of magic, the part of the area that is covered by the sphere is suppressed. For example, the flames created by a wall of fire are suppressed within the sphere, creating a gap in the wall if the overlap is large enough. Spells. Any active spell or other magical effect on a creature or an object in the sphere is suppressed while the creature or object is in it. Magic Items. The properties and powers of magic items are suppressed in the sphere. For example, a +1 longsword in the sphere functions as a nonmagical longsword. A magic weapons properties and powers are suppressed if it is used against a target in the sphere or wielded by an attacker in the sphere. If a magic weapon or a piece of magic ammunition fully leaves the sphere (for example, if you fire a magic arrow or throw a magic spear at a target outside the sphere), the magic of the item ceases to be suppressed as soon as it exits. Magical Travel. Teleportation and planar travel fail to work in the sphere, whether the sphere is the destination or the departure point for such magical travel. A portal to another location, world, or plane of existence, as well as an opening to an extradimensional space such as that created by the rope trick spell, temporarily closes while in the sphere. Creatures and Objects. A creature or object summoned or created by magic temporarily winks out of existence in the sphere. Such a creature instantly reappears once the space the creature occupied is no longer within the sphere. Dispel Magic. Spells and magical effects such as dispel magic have no effect on the sphere. Likewise, the spheres created by different antimagic field spells dont nullify each other.`,
duration : "Concentration, up to 1 hour",
concentration : true,
level : 8,
ritual : false,
range : "Self (10-foot-radius sphere)",
school : "Abjuration",
classes : [],
source : "Player's Handbook",
page : "pg.213"
}
];
module.exports = _.map(spells, (spell)=>{
spell.id = _.snakeCase(spell.name);
return spell;
});

View File

@@ -1,21 +1,16 @@
var _ = require('lodash');
var vitreumRender = require('vitreum/render');
console.log(JSON.stringify(require('./parsespell.js'), null, ' '));
module.exports = function(app){
app.get('/spellsort*', (req, res)=>{
//Edit Page
app.get('/tpk*', function(req, res){
vitreumRender({
page: './build/spellsort/bundle.dot',
page: './build/tpk/bundle.dot',
globals:{},
prerenderWith : './client/spellsort/spellsort.jsx',
prerenderWith : './client/tpk/tpk.jsx',
initialProps: {
url: req.originalUrl,
//spells : require('./spellsort.spells.js')
spells : require('./parsespell.js')
},
clearRequireCache : !process.env.PRODUCTION,
}, function (err, page) {

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, ' '));
*/

View File

@@ -68,6 +68,5 @@ X Print Dialog now auto-opens on print page
- should support `has:somatic`, `class:wizard`, `school:divination`, `level:6`
- Add a dropdown box with clickable elements to inject search terms
- Clean up the 5th edition spells json
- Split up the spells into separate josn based on the source