0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-14 15:03:07 +00:00

Nearly done, jsut need to clean up the json file and add local storage support

This commit is contained in:
Scott Tolksdorf
2015-11-16 00:52:29 -05:00
parent 3627ee3b49
commit e6e87457da
11 changed files with 214 additions and 44 deletions

View File

@@ -16,9 +16,9 @@ var Encounter = React.createClass({
desc : '', desc : '',
reward : '', reward : '',
enemies : [], enemies : [],
players : '',
index : {}, index : {},
monsterManual : {} monsterManual : {}
}; };
}, },
@@ -67,34 +67,60 @@ var Encounter = React.createClass({
}, },
getPlayerObjects : function(){
return _.map(this.props.players.split('\n'), function(line){
var parts = line.split(' ');
if(parts.length != 2) return null;
return {
name : parts[0],
initiative : parts[1] * 1,
isPC : true
}
})
},
renderEnemies : function(){ renderEnemies : function(){
var self = this; var self = this;
var sortedEnemies = _.sortBy(this.state.enemies, function(e){ var sortedEnemies = _.sortBy(_.union(_.values(this.state.enemies), this.getPlayerObjects()), function(e){
return -e.initiative; if(e.initiative) return -e.initiative;
return 0;
}); });
return _.map(sortedEnemies, function(enemy){ return _.map(sortedEnemies, function(enemy){
if(enemy.isPC){
return <PlayerCard {...enemy} key={enemy.name} />
}
return <MonsterCard return <MonsterCard
{...enemy} {...enemy}
key={enemy.id} key={enemy.id}
updateHP={self.updateHP.bind(self, enemy.id)} updateHP={self.updateHP.bind(self, enemy.id)}
remove={self.removeEnemy.bind(self, enemy.id)} /> remove={self.removeEnemy.bind(self, enemy.id)}
/>
}) })
}, },
render : function(){ render : function(){
var self = this; var self = this;
var reward;
if(this.props.reward){
reward = <div className='reward'>
<i className='fa fa-trophy' />
{this.props.reward}
</div>
}
return( return(
<div className='encounter'> <div className='mainEncounter'>
<div className='info'> <div className='info'>
{this.props.name} <h1>{this.props.name}</h1>
<p>{this.props.desc}</p>
{reward}
</div> </div>
<div className='cardContainer'> <div className='cardContainer'>
{this.renderEnemies()} {this.renderEnemies()}
</div> </div>
@@ -104,3 +130,21 @@ var Encounter = React.createClass({
}); });
module.exports = Encounter; module.exports = Encounter;
var PlayerCard = React.createClass({
getDefaultProps: function() {
return {
name : '',
initiative : 0
};
},
render : function(){
return <div className='playerCard'>
<span className='name'>{_.startCase(this.props.name)}</span>
<span className='initiative'><i className='fa fa-hourglass-2'/>{this.props.initiative}</span>
</div>
},
})

View File

@@ -1,3 +1,31 @@
.encounter{
.mainEncounter{
box-sizing : border-box;
overflow : hidden;
width : auto;
&>.info{
margin-left: 10px;
h1{
font-size: 2em;
font-weight: 800;
margin-bottom: 5px;
}
p{
font-size: 0.8em;
}
.reward{
font-size: 0.8em;
font-weight: 800;
margin-top: 5px;
i{
margin-right: 5px;
}
}
}
} }

View File

@@ -83,11 +83,11 @@ var AttackSlot = React.createClass({
<i className={cx('fa', { <i className={cx('fa', {
'fa-hand-grab-o' : type=='dmg', 'fa-hand-grab-o' : type=='dmg',
'fa-bullseye' : type=='atk', 'fa-bullseye' : type=='atk',
'fa-medkit' : type=='heal' 'fa-plus' : type=='heal'
})} /> })} />
{self.props[type]} {self.props[type]}
</button> </button>
<span key={self.state.lastRoll[type+'key']}>{self.state.lastRoll[type] || ''}</span> <span key={self.state.lastRoll[type+'key']}>{self.state.lastRoll[type]}</span>
</div> </div>
}) })

View File

@@ -87,6 +87,7 @@ var MonsterCard = React.createClass({
renderHPBox : function(){ renderHPBox : function(){
var self = this;
var tempHP var tempHP
if(this.state.tempHP){ if(this.state.tempHP){
@@ -98,17 +99,17 @@ var MonsterCard = React.createClass({
<div className='currentHP'> <div className='currentHP'>
{tempHP} {this.props.currentHP} {tempHP} {this.props.currentHP}
</div> </div>
<div className='maxHP'>{this.props.hp}</div> {self.renderStats()}
</div> </div>
}, },
renderStats : function(){ renderStats : function(){
var stats = { var stats = {
'fa fa-shield' : this.props.ac, 'fa fa-shield' : this.props.ac,
'fa fa-hourglass-2' : this.props.initiative, //'fa fa-hourglass-2' : this.props.initiative,
} }
return _.map(stats, function(val, icon){ return _.map(stats, function(val, icon){
return <span className='stat' key={icon}><i className={icon} /> {val}</span> return <div className='stat' key={icon}> {val} <i className={icon} /></div>
}) })
}, },
@@ -164,7 +165,6 @@ var MonsterCard = React.createClass({
{this.renderHPBox()} {this.renderHPBox()}
<div className='info'> <div className='info'>
<span className='name'>{this.props.name}</span> <span className='name'>{this.props.name}</span>
{this.renderStats()}
</div> </div>
<div className='attackContainer'> <div className='attackContainer'>

View File

@@ -7,13 +7,42 @@
-ms-user-select : none; -ms-user-select : none;
user-select : none; user-select : none;
} }
@marginSize : 10px;
.playerCard{
display : inline-block;
box-sizing : border-box;
margin : @marginSize;
padding : 10px;
background-color : white;
border : 1px solid #bbb;
.name{
margin-right: 20px;
}
.initiative{
font-size: 0.8em;
i{
font-size: 0.8em;
}
}
&:nth-child(5n + 1){ background-color: fade(@blue, 25%); }
&:nth-child(5n + 2){ background-color: fade(@purple, 25%); }
&:nth-child(5n + 3){ background-color: fade(@steel, 25%); }
&:nth-child(5n + 4){ background-color: fade(@green, 25%); }
&:nth-child(5n + 5){ background-color: fade(@orange, 25%); }
}
.monsterCard{ .monsterCard{
position : relative; position : relative;
display : inline-block; display : inline-block;
vertical-align : top; vertical-align : top;
box-sizing : border-box; box-sizing : border-box;
width : 250px; width : 220px;
margin : 30px; margin : @marginSize;
padding : 10px; padding : 10px;
background-color : white; background-color : white;
border : 1px solid #bbb; border : 1px solid #bbb;
@@ -81,7 +110,7 @@
line-height : 0.8em; line-height : 0.8em;
} }
} }
.maxHP{ .stat{
font-size : 0.8em; font-size : 0.8em;
} }
.hpText{ .hpText{

View File

@@ -105,6 +105,8 @@ var NaturalCrit = React.createClass({
encounters : encounters, encounters : encounters,
monsterManual : MonsterManual, monsterManual : MonsterManual,
players : 'jasper 13'
}; };
}, },
@@ -142,19 +144,21 @@ var NaturalCrit = React.createClass({
handleJSONChange : function(encounterIndex, json){ handleJSONChange : function(encounterIndex, json){
this.state.encounters[encounterIndex] = json; this.state.encounters[encounterIndex] = json;
this.setState({ this.setState({
encounters : this.state.encounters encounters : this.state.encounters
}) })
}, },
handleEncounterChange : function(encounterIndex){ handleEncounterChange : function(encounterIndex){
this.setState({ this.setState({
selectedEncounterIndex : encounterIndex selectedEncounterIndex : encounterIndex
}); });
}, },
handlePlayerChange : function(e){
this.setState({
players : e.target.value
});
},
renderSelectedEncounter : function(){ renderSelectedEncounter : function(){
@@ -169,6 +173,7 @@ var NaturalCrit = React.createClass({
key={selectedEncounter.name} key={selectedEncounter.name}
{...selectedEncounter} {...selectedEncounter}
monsterManual={this.state.monsterManual} monsterManual={this.state.monsterManual}
players={this.state.players}
/> />
} }
@@ -187,14 +192,15 @@ var NaturalCrit = React.createClass({
selectedEncounter={this.state.selectedEncounterIndex} selectedEncounter={this.state.selectedEncounterIndex}
encounters={this.state.encounters} encounters={this.state.encounters}
monsterManual={this.state.monsterManual} monsterManual={this.state.monsterManual}
players={this.state.players}
onSelectEncounter={this.handleEncounterChange} onSelectEncounter={this.handleEncounterChange}
onJSONChange={this.handleJSONChange} onJSONChange={this.handleJSONChange}
onPlayerChange={this.handlePlayerChange}
/> />
<div className='encounterContainer'>
{this.renderSelectedEncounter()} {this.renderSelectedEncounter()}
</div>
</div> </div>
); );

View File

@@ -3,6 +3,9 @@
@import 'naturalCrit/animations.less'; @import 'naturalCrit/animations.less';
@import 'naturalCrit/colors.less'; @import 'naturalCrit/colors.less';
@sidebarWidth : 250px;
body{ body{
background-color : #eee; background-color : #eee;
font-family : 'Open Sans', sans-serif; font-family : 'Open Sans', sans-serif;

View File

@@ -14,8 +14,9 @@ var Sidebar = React.createClass({
encounters : [], encounters : [],
onSelectEncounter : function(){}, onSelectEncounter : function(){},
onJSONChange : function(encounterIndex, json){}, onJSONChange : function(encounterIndex, json){},
onPlayerChange : function(){},
}; };
}, },
@@ -48,7 +49,6 @@ var Sidebar = React.createClass({
var self = this; var self = this;
return _.map(this.props.encounters, function(encounter, index){ return _.map(this.props.encounters, function(encounter, index){
console.log(self.props.selectedEncounter, index);
var isSelected = self.props.selectedEncounter == index; var isSelected = self.props.selectedEncounter == index;
return <div className={cx('encounter' , {'selected' : isSelected})} key={index}> return <div className={cx('encounter' , {'selected' : isSelected})} key={index}>
@@ -85,13 +85,17 @@ var Sidebar = React.createClass({
<JSONFileEditor name="Monster Manual" /> <JSONFileEditor name="Monster Manual" />
</div> </div>
<div className='encounterContainer'> <div className='encounterContainer'>
<h3> <i className='fa fa-flag' /> encounters </h3> <h3> <i className='fa fa-flag' /> Encounters </h3>
{this.renderEncounters()} {this.renderEncounters()}
</div>
<div className='encounterStats'>
<div className='controls'>
</div>
</div> </div>
<div className='addPC'> <div className='addPlayers'>
<h3> <i className='fa fa-group' /> Players </h3>
<textarea value={this.props.players} onChange={this.props.onPlayerChange} />
</div> </div>

View File

@@ -9,19 +9,22 @@
} }
.sidebar{ .sidebar{
.animateAll(); .animateAll();
display : inline-block; float : left;
vertical-align : top; box-sizing : border-box;
box-sizing : border-box; height : 100%;
height : 100%; width : @sidebarWidth;
width : 300px; padding-bottom : 20px;
background-color : white;
//border : 1px solid @steel;
&.hide{ &.hide{
height : 50px; height : 50px;
width : 50px; width : 50px;
.logo .name{ .logo .name{
left : -200px; left : -200px;
opacity : 0;
} }
.contents{ .contents{
height : 0px;
opacity : 0; opacity : 0;
} }
} }
@@ -43,27 +46,45 @@
position : absolute; position : absolute;
top : 13px; top : 13px;
left : 50px; left : 50px;
opacity : 1;
span.crit{ span.crit{
font-family : 'CodeBold'; font-family : 'CodeBold';
} }
} }
} }
.contents{ .contents{
.animate(opacity); .animate(opacity);
width : 100%; box-sizing : border-box;
width : 100%;
&>*{ &>*{
width : 100%; width : 100%;
} }
h3{
padding : 10px;
font-size : 0.8em;
font-weight : 800;
text-transform : uppercase;
}
.encounterContainer{ .encounterContainer{
margin-bottom : 20px;
h3{
background-color : fade(@red, 25%);
}
.encounter{ .encounter{
&.selected{ &.selected{
background-color : @green; background-color : @green;
} }
} }
} }
.addPlayers{
h3{
background-color : fade(@purple, 25%);
}
textarea{
height : 80px;
width : 100px;
margin : 10px;
}
}
} }
} }

View File

@@ -15,6 +15,19 @@ var json = {
} }
} }
var downloadFile = function(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
var JsonFileEditor = React.createClass({ var JsonFileEditor = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
@@ -62,10 +75,23 @@ var JsonFileEditor = React.createClass({
}, },
handleDownload : function(){ handleDownload : function(){
downloadFile(this.props.name + '.json', JSON.stringify(this.props.json, null, '\t'));
},
handleUpload : function(e){
var self = this;
var reader = new FileReader();
reader.onload = function() {
self.props.onJSONChange(JSON.parse(reader.result));
}
reader.readAsText(e.target.files[0]);
},
handleUploadClick : function(){
this.refs.uploader.click()
}, },
handleRemove : function(){ handleRemove : function(){
}, },
@@ -87,17 +113,21 @@ var JsonFileEditor = React.createClass({
<button className='showEditor' onClick={this.handleShowEditorClick}><i className='fa fa-edit' /></button> <button className='showEditor' onClick={this.handleShowEditorClick}><i className='fa fa-edit' /></button>
<button className='downloadJSON' onClick={this.handleDownload}><i className='fa fa-download' /></button> <button className='downloadJSON' onClick={this.handleDownload}><i className='fa fa-download' /></button>
<div className='remove' onClick={this.handleRemove}><i className='fa fa-times' /></div> <button className='uploadJSON' onClick={this.handleUploadClick}><i className='fa fa-cloud-upload' /></button>
</div> </div>
{this.renderEditor()} {this.renderEditor()}
<input type="file" id="input" onChange={this.handleUpload} ref='uploader' />
</div> </div>
); );
} }
}); });
module.exports = JsonFileEditor; module.exports = JsonFileEditor;
//<div className='remove' onClick={this.handleRemove}><i className='fa fa-times' /></div>

View File

@@ -26,5 +26,10 @@
position : absolute; position : absolute;
top : 0px; top : 0px;
right : 0px; right : 0px;
}
input[type="file"]{
display: none;
} }
} }