mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-29 04:42:41 +00:00
Nearly done, jsut need to clean up the json file and add local storage support
This commit is contained in:
@@ -16,9 +16,9 @@ var Encounter = React.createClass({
|
||||
desc : '',
|
||||
reward : '',
|
||||
enemies : [],
|
||||
players : '',
|
||||
index : {},
|
||||
|
||||
|
||||
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(){
|
||||
var self = this;
|
||||
|
||||
var sortedEnemies = _.sortBy(this.state.enemies, function(e){
|
||||
return -e.initiative;
|
||||
var sortedEnemies = _.sortBy(_.union(_.values(this.state.enemies), this.getPlayerObjects()), function(e){
|
||||
if(e.initiative) return -e.initiative;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return _.map(sortedEnemies, function(enemy){
|
||||
if(enemy.isPC){
|
||||
return <PlayerCard {...enemy} key={enemy.name} />
|
||||
}
|
||||
|
||||
return <MonsterCard
|
||||
{...enemy}
|
||||
key={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(){
|
||||
var self = this;
|
||||
|
||||
var reward;
|
||||
if(this.props.reward){
|
||||
reward = <div className='reward'>
|
||||
<i className='fa fa-trophy' />
|
||||
{this.props.reward}
|
||||
</div>
|
||||
}
|
||||
|
||||
return(
|
||||
<div className='encounter'>
|
||||
|
||||
<div className='mainEncounter'>
|
||||
<div className='info'>
|
||||
{this.props.name}
|
||||
<h1>{this.props.name}</h1>
|
||||
<p>{this.props.desc}</p>
|
||||
{reward}
|
||||
</div>
|
||||
|
||||
|
||||
<div className='cardContainer'>
|
||||
{this.renderEnemies()}
|
||||
</div>
|
||||
@@ -104,3 +130,21 @@ var Encounter = React.createClass({
|
||||
});
|
||||
|
||||
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>
|
||||
},
|
||||
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -83,11 +83,11 @@ var AttackSlot = React.createClass({
|
||||
<i className={cx('fa', {
|
||||
'fa-hand-grab-o' : type=='dmg',
|
||||
'fa-bullseye' : type=='atk',
|
||||
'fa-medkit' : type=='heal'
|
||||
'fa-plus' : type=='heal'
|
||||
})} />
|
||||
{self.props[type]}
|
||||
</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>
|
||||
})
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ var MonsterCard = React.createClass({
|
||||
|
||||
|
||||
renderHPBox : function(){
|
||||
var self = this;
|
||||
|
||||
var tempHP
|
||||
if(this.state.tempHP){
|
||||
@@ -98,17 +99,17 @@ var MonsterCard = React.createClass({
|
||||
<div className='currentHP'>
|
||||
{tempHP} {this.props.currentHP}
|
||||
</div>
|
||||
<div className='maxHP'>{this.props.hp}</div>
|
||||
{self.renderStats()}
|
||||
</div>
|
||||
},
|
||||
|
||||
renderStats : function(){
|
||||
var stats = {
|
||||
'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 <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()}
|
||||
<div className='info'>
|
||||
<span className='name'>{this.props.name}</span>
|
||||
{this.renderStats()}
|
||||
</div>
|
||||
|
||||
<div className='attackContainer'>
|
||||
|
||||
@@ -7,13 +7,42 @@
|
||||
-ms-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{
|
||||
position : relative;
|
||||
display : inline-block;
|
||||
vertical-align : top;
|
||||
box-sizing : border-box;
|
||||
width : 250px;
|
||||
margin : 30px;
|
||||
width : 220px;
|
||||
margin : @marginSize;
|
||||
padding : 10px;
|
||||
background-color : white;
|
||||
border : 1px solid #bbb;
|
||||
@@ -81,7 +110,7 @@
|
||||
line-height : 0.8em;
|
||||
}
|
||||
}
|
||||
.maxHP{
|
||||
.stat{
|
||||
font-size : 0.8em;
|
||||
}
|
||||
.hpText{
|
||||
|
||||
@@ -105,6 +105,8 @@ var NaturalCrit = React.createClass({
|
||||
encounters : encounters,
|
||||
monsterManual : MonsterManual,
|
||||
|
||||
players : 'jasper 13'
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
@@ -142,19 +144,21 @@ var NaturalCrit = React.createClass({
|
||||
|
||||
|
||||
handleJSONChange : function(encounterIndex, json){
|
||||
|
||||
this.state.encounters[encounterIndex] = json;
|
||||
this.setState({
|
||||
encounters : this.state.encounters
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
handleEncounterChange : function(encounterIndex){
|
||||
this.setState({
|
||||
selectedEncounterIndex : encounterIndex
|
||||
});
|
||||
},
|
||||
handlePlayerChange : function(e){
|
||||
this.setState({
|
||||
players : e.target.value
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
renderSelectedEncounter : function(){
|
||||
@@ -169,6 +173,7 @@ var NaturalCrit = React.createClass({
|
||||
key={selectedEncounter.name}
|
||||
{...selectedEncounter}
|
||||
monsterManual={this.state.monsterManual}
|
||||
players={this.state.players}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -187,14 +192,15 @@ var NaturalCrit = React.createClass({
|
||||
selectedEncounter={this.state.selectedEncounterIndex}
|
||||
encounters={this.state.encounters}
|
||||
monsterManual={this.state.monsterManual}
|
||||
players={this.state.players}
|
||||
|
||||
onSelectEncounter={this.handleEncounterChange}
|
||||
onJSONChange={this.handleJSONChange}
|
||||
onPlayerChange={this.handlePlayerChange}
|
||||
/>
|
||||
|
||||
<div className='encounterContainer'>
|
||||
|
||||
{this.renderSelectedEncounter()}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
@import 'naturalCrit/animations.less';
|
||||
@import 'naturalCrit/colors.less';
|
||||
|
||||
|
||||
@sidebarWidth : 250px;
|
||||
|
||||
body{
|
||||
background-color : #eee;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
|
||||
@@ -14,8 +14,9 @@ var Sidebar = React.createClass({
|
||||
encounters : [],
|
||||
|
||||
onSelectEncounter : function(){},
|
||||
|
||||
onJSONChange : function(encounterIndex, json){},
|
||||
|
||||
onPlayerChange : function(){},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -48,7 +49,6 @@ var Sidebar = React.createClass({
|
||||
var self = this;
|
||||
|
||||
return _.map(this.props.encounters, function(encounter, index){
|
||||
console.log(self.props.selectedEncounter, index);
|
||||
|
||||
var isSelected = self.props.selectedEncounter == index;
|
||||
return <div className={cx('encounter' , {'selected' : isSelected})} key={index}>
|
||||
@@ -85,13 +85,17 @@ var Sidebar = React.createClass({
|
||||
<JSONFileEditor name="Monster Manual" />
|
||||
</div>
|
||||
<div className='encounterContainer'>
|
||||
<h3> <i className='fa fa-flag' /> encounters </h3>
|
||||
<h3> <i className='fa fa-flag' /> Encounters </h3>
|
||||
{this.renderEncounters()}
|
||||
</div>
|
||||
<div className='encounterStats'>
|
||||
|
||||
<div className='controls'>
|
||||
|
||||
</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>
|
||||
|
||||
|
||||
@@ -9,19 +9,22 @@
|
||||
}
|
||||
.sidebar{
|
||||
.animateAll();
|
||||
display : inline-block;
|
||||
vertical-align : top;
|
||||
box-sizing : border-box;
|
||||
height : 100%;
|
||||
width : 300px;
|
||||
|
||||
float : left;
|
||||
box-sizing : border-box;
|
||||
height : 100%;
|
||||
width : @sidebarWidth;
|
||||
padding-bottom : 20px;
|
||||
background-color : white;
|
||||
//border : 1px solid @steel;
|
||||
&.hide{
|
||||
height : 50px;
|
||||
width : 50px;
|
||||
.logo .name{
|
||||
left : -200px;
|
||||
left : -200px;
|
||||
opacity : 0;
|
||||
}
|
||||
.contents{
|
||||
height : 0px;
|
||||
opacity : 0;
|
||||
}
|
||||
}
|
||||
@@ -43,27 +46,45 @@
|
||||
position : absolute;
|
||||
top : 13px;
|
||||
left : 50px;
|
||||
opacity : 1;
|
||||
span.crit{
|
||||
font-family : 'CodeBold';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contents{
|
||||
.animate(opacity);
|
||||
width : 100%;
|
||||
box-sizing : border-box;
|
||||
width : 100%;
|
||||
&>*{
|
||||
width : 100%;
|
||||
}
|
||||
|
||||
h3{
|
||||
padding : 10px;
|
||||
font-size : 0.8em;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
.encounterContainer{
|
||||
margin-bottom : 20px;
|
||||
h3{
|
||||
background-color : fade(@red, 25%);
|
||||
}
|
||||
.encounter{
|
||||
&.selected{
|
||||
background-color : @green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.addPlayers{
|
||||
h3{
|
||||
background-color : fade(@purple, 25%);
|
||||
}
|
||||
textarea{
|
||||
height : 80px;
|
||||
width : 100px;
|
||||
margin : 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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({
|
||||
getDefaultProps: function() {
|
||||
@@ -62,10 +75,23 @@ var JsonFileEditor = React.createClass({
|
||||
},
|
||||
|
||||
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(){
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
@@ -87,17 +113,21 @@ var JsonFileEditor = React.createClass({
|
||||
|
||||
<button className='showEditor' onClick={this.handleShowEditorClick}><i className='fa fa-edit' /></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>
|
||||
|
||||
|
||||
{this.renderEditor()}
|
||||
|
||||
<input type="file" id="input" onChange={this.handleUpload} ref='uploader' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = JsonFileEditor;
|
||||
|
||||
//<div className='remove' onClick={this.handleRemove}><i className='fa fa-times' /></div>
|
||||
@@ -26,5 +26,10 @@
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
|
||||
}
|
||||
|
||||
input[type="file"]{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user