mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-25 16:15:53 +00:00
Compare commits
374 Commits
phantom
...
newRendere
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
570d86448d | ||
|
|
18d0b68eb0 | ||
|
|
74d17f32a5 | ||
|
|
469fa957aa | ||
|
|
4d0ebcb1d8 | ||
|
|
10f9ac14c7 | ||
|
|
705adbe2c6 | ||
|
|
7e7428504e | ||
|
|
c28647726a | ||
|
|
a30bd79399 | ||
|
|
87a9c46202 | ||
|
|
380bbd661f | ||
|
|
fa203047da | ||
|
|
b39f9041c2 | ||
|
|
2023ae4f6a | ||
|
|
d5f04ca2b6 | ||
|
|
f99bcabad0 | ||
|
|
32317bfa6f | ||
|
|
1946a50ce0 | ||
|
|
ee1827eab0 | ||
|
|
20371a8b3d | ||
|
|
28a3f31caa | ||
|
|
c647bdf5ee | ||
|
|
eb1827cedb | ||
|
|
a3251dfa19 | ||
|
|
30d3fcf168 | ||
|
|
393df1b181 | ||
|
|
94a3a96960 | ||
|
|
bfb2cea48e | ||
|
|
00f2703d0b | ||
|
|
4c874149fb | ||
|
|
0705e08381 | ||
|
|
e112808706 | ||
|
|
234d216d64 | ||
|
|
ef0265f4fa | ||
|
|
fbc18a017c | ||
|
|
9d4d337bb9 | ||
|
|
0d0ce101f3 | ||
|
|
540c00cb0c | ||
|
|
446ae9cbcf | ||
|
|
dc486cfba9 | ||
|
|
a6a1f41e77 | ||
|
|
fd567352a4 | ||
|
|
a33b1d845d | ||
|
|
b20f4ffb46 | ||
|
|
2f69ef3fe8 | ||
|
|
1da1f90a35 | ||
|
|
bd08858745 | ||
|
|
304cd0ffcd | ||
|
|
b40e5bc4c4 | ||
|
|
0663737e1c | ||
|
|
307dd2d9ba | ||
|
|
95c91b6ba8 | ||
|
|
c8c46725a2 | ||
|
|
7001b71d91 | ||
|
|
cbab4f4959 | ||
|
|
22d9982888 | ||
|
|
76ced9ca49 | ||
|
|
b1db8040a4 | ||
|
|
c8b089f7fb | ||
|
|
97c0443c76 | ||
|
|
c470bed591 | ||
|
|
4593099914 | ||
|
|
6030134de2 | ||
|
|
7e6f42f062 | ||
|
|
75111acefb | ||
|
|
26bcb3395a | ||
|
|
a826aaffd9 | ||
|
|
8018442f25 | ||
|
|
8e58e5aca9 | ||
|
|
2f82d3875e | ||
|
|
efee8ff05c | ||
|
|
a405c7cfb2 | ||
|
|
dfcb04fd09 | ||
|
|
728277f861 | ||
|
|
0878439750 | ||
|
|
e77532acef | ||
|
|
cd2eb5fdce | ||
|
|
37de888f03 | ||
|
|
1aa79b32d9 | ||
|
|
894e345a44 | ||
|
|
07f249b23e | ||
|
|
baaa82ed34 | ||
|
|
0d0f0d8eb0 | ||
|
|
d77fa0a3dc | ||
|
|
a26c4e2092 | ||
|
|
ca40ec5a2d | ||
|
|
987363ed41 | ||
|
|
7b38bccec1 | ||
|
|
174c2973f7 | ||
|
|
66ca09b36d | ||
|
|
5820564894 | ||
|
|
3dc4c13178 | ||
|
|
537a75b2ab | ||
|
|
a0bc4fddf8 | ||
|
|
25e0a1607a | ||
|
|
68ecf749ea | ||
|
|
10f4759471 | ||
|
|
5ba3f98696 | ||
|
|
95c09ba7ad | ||
|
|
1173af5803 | ||
|
|
924b398768 | ||
|
|
41303e6918 | ||
|
|
f75f60aa1e | ||
|
|
f4cf288f27 | ||
|
|
8abf6abf99 | ||
|
|
95aa803c61 | ||
|
|
47396e5c7e | ||
|
|
7581d155a6 | ||
|
|
cc8e874ad1 | ||
|
|
8b148b81b8 | ||
|
|
063d701a0e | ||
|
|
296b066ed3 | ||
|
|
9fe6fd979b | ||
|
|
70346ffce7 | ||
|
|
c417c1aa0c | ||
|
|
5e04e5dc99 | ||
|
|
6544d63b23 | ||
|
|
5bfa195dc7 | ||
|
|
9b936f42f3 | ||
|
|
5fe7c7a6d8 | ||
|
|
6e86d9c123 | ||
|
|
306e4f024c | ||
|
|
a2c6940be4 | ||
|
|
75f83427e0 | ||
|
|
4be9f4a74a | ||
|
|
9a9e0cc5bc | ||
|
|
62e4997fa9 | ||
|
|
b45ec32e44 | ||
|
|
9fd7586726 | ||
|
|
75f0a9f755 | ||
|
|
a35d684773 | ||
|
|
9b80f17564 | ||
|
|
90a6ac4ef8 | ||
|
|
bd6ba1e497 | ||
|
|
56048ab936 | ||
|
|
1ec0b2fa91 | ||
|
|
fe4a05dc25 | ||
|
|
1f1bd669fe | ||
|
|
63aee2dedf | ||
|
|
e5ffb7c629 | ||
|
|
be783e5f6b | ||
|
|
d96ac0f3ca | ||
|
|
ce5596c489 | ||
|
|
9285e53e55 | ||
|
|
bec417a325 | ||
|
|
1eeb1127b1 | ||
|
|
8e6fccc969 | ||
|
|
a634b76117 | ||
|
|
30942785d1 | ||
|
|
21d3c5bfc8 | ||
|
|
7f639a0824 | ||
|
|
a0ca45ce1c | ||
|
|
4e5cd914f7 | ||
|
|
2d6b89c769 | ||
|
|
6af9dd9325 | ||
|
|
9e14872f06 | ||
|
|
750f5c1330 | ||
|
|
1db24d07bd | ||
|
|
f4f96253c2 | ||
|
|
97cf2d2ce7 | ||
|
|
e61bf22788 | ||
|
|
ccdb6a3cbd | ||
|
|
d400c37b6d | ||
|
|
2f2a1c5146 | ||
|
|
8b3f9ac76a | ||
|
|
6672dff938 | ||
|
|
d7463ec28e | ||
|
|
4dce90ab41 | ||
|
|
5f6f7ec691 | ||
|
|
582deb7bf7 | ||
|
|
8c4a6221e2 | ||
|
|
58c67d7d4d | ||
|
|
9fe52145e6 | ||
|
|
b671161044 | ||
|
|
a439c418ef | ||
|
|
b8effb1ed1 | ||
|
|
e576e6d971 | ||
|
|
9f05556bc5 | ||
|
|
2a0c06cd3d | ||
|
|
dd1264d2e6 | ||
|
|
c49321d590 | ||
|
|
cd2337ff2c | ||
|
|
9eb26a95f3 | ||
|
|
c7ccf6747c | ||
|
|
9a96eebdb1 | ||
|
|
54542a8ec1 | ||
|
|
efddfd31ee | ||
|
|
0690216222 | ||
|
|
9e54fe26d9 | ||
|
|
01e4e11ef7 | ||
|
|
7dc39e962b | ||
|
|
9adf28171e | ||
|
|
152194aba8 | ||
|
|
a107d59d72 | ||
|
|
e6be1ae12f | ||
|
|
460585f9c9 | ||
|
|
143c006298 | ||
|
|
43ec212be9 | ||
|
|
78426135c6 | ||
|
|
b134e11a86 | ||
|
|
888d3faa4c | ||
|
|
ef8784ccf2 | ||
|
|
0a7b538216 | ||
|
|
019383ebdd | ||
|
|
90c695c005 | ||
|
|
6b337b5d69 | ||
|
|
62d70022e7 | ||
|
|
cd454e82ef | ||
|
|
8a7e441724 | ||
|
|
1482501574 | ||
|
|
f22fd73162 | ||
|
|
91c7f45d33 | ||
|
|
34b21703e1 | ||
|
|
73e561beba | ||
|
|
388ae933f8 | ||
|
|
33fd991276 | ||
|
|
e31cb003eb | ||
|
|
46581cfcf5 | ||
|
|
073b547f96 | ||
|
|
3ff736b440 | ||
|
|
ead975b605 | ||
|
|
cd97b22067 | ||
|
|
e38850f807 | ||
|
|
8df4dc56b2 | ||
|
|
235664ec88 | ||
|
|
3e796501e2 | ||
|
|
0d25a972ba | ||
|
|
5969e45087 | ||
|
|
f058814040 | ||
|
|
038c89fc20 | ||
|
|
d9bf164010 | ||
|
|
39d5d5c458 | ||
|
|
d2a9b3f274 | ||
|
|
7ca4e8ffa6 | ||
|
|
60092f404c | ||
|
|
c50de28900 | ||
|
|
6611bc9eff | ||
|
|
802103ff27 | ||
|
|
9950c747da | ||
|
|
2b2b6b2793 | ||
|
|
6474825ffb | ||
|
|
7eb47d7db0 | ||
|
|
b8c55d72a1 | ||
|
|
87af0e8cb7 | ||
|
|
63dcbfa388 | ||
|
|
8a7bffce5d | ||
|
|
715f2f41bb | ||
|
|
b33ef939ed | ||
|
|
00bf12ecb7 | ||
|
|
c993ae9cf5 | ||
|
|
626cba6062 | ||
|
|
14b7d60856 | ||
|
|
5a1041cbb3 | ||
|
|
56bb0e0ad8 | ||
|
|
dce3f224c7 | ||
|
|
01e040d1ab | ||
|
|
fa138499af | ||
|
|
7cc70149da | ||
|
|
2ed9395e29 | ||
|
|
53529d14eb | ||
|
|
7501342e85 | ||
|
|
68fc0f95d1 | ||
|
|
52ff2e41e6 | ||
|
|
e7f1083edb | ||
|
|
6209fcd5cb | ||
|
|
fc3587d6cb | ||
|
|
5f55a59042 | ||
|
|
0612fc63e6 | ||
|
|
23cd7e7516 | ||
|
|
81ea2c838e | ||
|
|
d9015c399c | ||
|
|
96a59780c2 | ||
|
|
eef826f8d8 | ||
|
|
8c29be5df5 | ||
|
|
a1b52d79f9 | ||
|
|
56987e7b60 | ||
|
|
451bbfc915 | ||
|
|
a81967884d | ||
|
|
801bde04c5 | ||
|
|
c084cb2d8b | ||
|
|
641ad747d3 | ||
|
|
cd280eb8f0 | ||
|
|
5537d974ff | ||
|
|
c4c09f0a69 | ||
|
|
9c1fd5b13a | ||
|
|
c1d7443c87 | ||
|
|
b464de69e3 | ||
|
|
ca377a2861 | ||
|
|
f29073e5ca | ||
|
|
634c1a617c | ||
|
|
6f6f5649d4 | ||
|
|
c9bfd08bb3 | ||
|
|
c07c9911ec | ||
|
|
eb4117b35b | ||
|
|
743bcb0c74 | ||
|
|
ed7decb42b | ||
|
|
d5b8c60317 | ||
|
|
15ad171c2d | ||
|
|
eca3ada8eb | ||
|
|
1bd85e80ee | ||
|
|
30e6bb28ad | ||
|
|
62654102b8 | ||
|
|
34a93a6151 | ||
|
|
62470f2958 | ||
|
|
12e1059071 | ||
|
|
413358feaa | ||
|
|
6a02e3d0cf | ||
|
|
cf8bcd2bb4 | ||
|
|
8aef39a81e | ||
|
|
a92adc0e53 | ||
|
|
7c8fd42619 | ||
|
|
ad02f99ebe | ||
|
|
c418ea5b42 | ||
|
|
f38f76b7c4 | ||
|
|
4139a8ee12 | ||
|
|
133dd7c144 | ||
|
|
1db6553365 | ||
|
|
582602740f | ||
|
|
5ba8489a42 | ||
|
|
4b482a8f0b | ||
|
|
1ce0f00b62 | ||
|
|
75fb606097 | ||
|
|
e6a747210e | ||
|
|
1949a5cca5 | ||
|
|
7f14870732 | ||
|
|
ead50af34b | ||
|
|
543ab39844 | ||
|
|
5b96fc03b6 | ||
|
|
558b9881d5 | ||
|
|
acf7a174f5 | ||
|
|
f35950c2c4 | ||
|
|
8688b99bdf | ||
|
|
4294f81f30 | ||
|
|
3af6d8763e | ||
|
|
bfa0567aad | ||
|
|
27c42caa4d | ||
|
|
207aa87253 | ||
|
|
6f4d71083c | ||
|
|
e988257c87 | ||
|
|
00dbd549f3 | ||
|
|
c93c6b13c4 | ||
|
|
90d46f5c48 | ||
|
|
d2bcaecce9 | ||
|
|
4964dda3a6 | ||
|
|
0379bf1720 | ||
|
|
d2424d839a | ||
|
|
62448043c4 | ||
|
|
23d118382b | ||
|
|
f511bdf050 | ||
|
|
256e62095c | ||
|
|
0a8c489132 | ||
|
|
f14786c602 | ||
|
|
482839731c | ||
|
|
47d5cecd31 | ||
|
|
441bc0c004 | ||
|
|
267b88b225 | ||
|
|
8d61a21fa7 | ||
|
|
ac8579ccc9 | ||
|
|
47fd832a32 | ||
|
|
30dab729bb | ||
|
|
fd871aa04a | ||
|
|
a1b8d4e8ce | ||
|
|
2231dc3684 | ||
|
|
c8b3a0f183 | ||
|
|
69f8cdb402 | ||
|
|
fe806b0636 | ||
|
|
273a1a1b00 | ||
|
|
9b84913c8f | ||
|
|
de3502a419 | ||
|
|
d9c20cebfe | ||
|
|
7bab7f42c4 | ||
|
|
df98beb0e5 | ||
|
|
533c09670f |
5
.github/issue_template.md
vendored
Normal file
5
.github/issue_template.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
Share link to issue brew: http://homebrewery.naturalcrit.com/share/XXXXXXX
|
||||
|
||||
|
||||
|
||||
|
||||
30
.gitignore
vendored
30
.gitignore
vendored
@@ -1,14 +1,16 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
#Ignore our built files
|
||||
build/*
|
||||
architecture.json
|
||||
|
||||
# Ignore sensitive stuff
|
||||
/config/*
|
||||
!/config/default.json
|
||||
|
||||
node_modules
|
||||
storage
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
#Ignore our built files
|
||||
build/*
|
||||
|
||||
# Ignore sensitive stuff
|
||||
config/local.json
|
||||
|
||||
node_modules
|
||||
storage
|
||||
.idea
|
||||
*.swp
|
||||
|
||||
todo.md
|
||||
55
README.md
55
README.md
@@ -1,22 +1,33 @@
|
||||
# NaturalCrit
|
||||
A tool suite for DMs to use for D&D
|
||||
|
||||
|
||||
### Getting started
|
||||
1. Make sure you have [node](https://nodejs.org/en/)
|
||||
1. Clone down the repo
|
||||
1. In your terminal, head to the repo
|
||||
1. Run `npm install` to get all the dependacies
|
||||
2. Run `npm install -g gulp` to install the gulp build tool
|
||||
1. Run `gulp fresh`, this will compile and build all the needed libraries (this only has to be done once, unless you add more libs)
|
||||
1. Run `gulp` to run the project locally. Should be accessible at `localhost:8000`
|
||||
2. Any changes to files within the proejct will be detected and the propject will automatically re-build
|
||||
|
||||
**Notes:** If you'd like to create and edit homebrews, you'll need to have MongoDB installed and running.
|
||||
|
||||
Have fun!
|
||||
|
||||
|
||||
### changelog
|
||||
|
||||
You can check out the changelog [here](https://github.com/stolksdorf/NaturalCrit/blob/master/changelog.md)
|
||||
# The Homebrewery
|
||||
The Homebrewery is a tool for making authnetic looking [D&D content](http://dnd.wizards.com/products/tabletop-games/rpg-products/rpg_playershandbook) using only [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). Check it out [here](http://homebrewery.naturalcrit.com).
|
||||
|
||||
|
||||
### issues, suggestions, bugs
|
||||
If you run into any issues using The Homebrewery, please submit an issues [here](/issues)
|
||||
|
||||
|
||||
### local dev
|
||||
Homebrewery is open source, so feel free to clone it, tinker with it, or run your own local version.
|
||||
|
||||
#### pre-reqs
|
||||
1. install [node](https://nodejs.org/en/)
|
||||
1. install [mongodb](https://www.mongodb.com/)
|
||||
|
||||
#### getting started
|
||||
1. clone it
|
||||
1. `npm install`
|
||||
1. `npm build`
|
||||
1. `npm start`
|
||||
|
||||
#### standalone PHB stylesheet
|
||||
If you just want the stylesheet that is generated to make pages look like they are from the PLayer's Handbook, you have find it [here](https://github.com/stolksdorf/homebrewery/blob/master/phb.standalone.css)
|
||||
|
||||
If you are developing locally and would like to generate your own, follow the above steps and then run `npm run phb`.
|
||||
|
||||
### changelog
|
||||
|
||||
You can check out the changelog [here](https://github.com/stolksdorf/homebrewery/blob/master/changelog.md)
|
||||
|
||||
### license
|
||||
|
||||
This project is licensed under [MIT](./license)
|
||||
|
||||
21
changelog.md
21
changelog.md
@@ -1,21 +0,0 @@
|
||||
## changelog
|
||||
|
||||
#### Sunday, 17/01/2016
|
||||
* Added a printer friendly snippet that injects some CSS to remove backbrounds and images
|
||||
* Adjusted the styling specific to spell blocks to give it tighter spacing
|
||||
* Added a changelog! How meta!
|
||||
|
||||
#### Thursday, 14/01/2016
|
||||
|
||||
* Added view source to see the markdown that made the page
|
||||
* Added print view
|
||||
* Fixed API issues that were causing the server to crash
|
||||
* Increased padding on table cells
|
||||
* Raw html now shows in view source
|
||||
|
||||
#### Wednesday, 3/01/2016
|
||||
|
||||
* Added `phb.standalone.css` plus a build system for creating it
|
||||
* Added page numbers and footer text
|
||||
* Page accent now flips each page
|
||||
|
||||
@@ -1,41 +1,42 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var HomebrewAdmin = require('./homebrewAdmin/homebrewAdmin.jsx');
|
||||
|
||||
var Admin = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
url : "",
|
||||
admin_key : "",
|
||||
homebrews : [],
|
||||
};
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className='admin'>
|
||||
|
||||
<header>
|
||||
<div className='container'>
|
||||
<i className='fa fa-rocket' />
|
||||
naturalcrit admin
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className='container'>
|
||||
|
||||
<a target="_blank" href='https://www.google.com/analytics/web/?hl=en#report/defaultid/a72212009w109843310p114529111/'>Link to Google Analytics</a>
|
||||
|
||||
<HomebrewAdmin homebrews={this.props.homebrews} admin_key={this.props.admin_key} />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Admin;
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const BrewLookup = require('./brewLookup/brewLookup.jsx');
|
||||
const AdminSearch = require('./adminSearch/adminSearch.jsx');
|
||||
const InvalidBrew = require('./invalidBrew/invalidBrew.jsx');
|
||||
|
||||
const Admin = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
admin_key : '',
|
||||
};
|
||||
},
|
||||
|
||||
renderNavbar : function(){
|
||||
return <Nav.base>
|
||||
<Nav.section>
|
||||
<Nav.item icon='fa-magic' className='homebreweryLogo'>
|
||||
Homebrewery Admin
|
||||
</Nav.item>
|
||||
</Nav.section>
|
||||
</Nav.base>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='admin'>
|
||||
{this.renderNavbar()}
|
||||
<main className='content'>
|
||||
<BrewLookup adminKey={this.props.admin_key} />
|
||||
<AdminSearch adminKey={this.props.admin_key} />
|
||||
|
||||
<div className='dangerZone'>Danger Zone</div>
|
||||
|
||||
<InvalidBrew adminKey={this.props.admin_key} />
|
||||
</main>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Admin;
|
||||
|
||||
@@ -1,39 +1,53 @@
|
||||
@import 'naturalCrit/styles/reset.less';
|
||||
@import 'naturalCrit/styles/elements.less';
|
||||
@import 'naturalCrit/styles/animations.less';
|
||||
@import 'naturalCrit/styles/colors.less';
|
||||
@import 'naturalCrit/styles/tooltip.less';
|
||||
|
||||
@import 'font-awesome/css/font-awesome.css';
|
||||
|
||||
html,body, #reactContainer, .naturalCrit{
|
||||
min-height : 100%;
|
||||
}
|
||||
|
||||
@sidebarWidth : 250px;
|
||||
|
||||
body{
|
||||
background-color : #eee;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
color : #4b5055;
|
||||
font-weight : 100;
|
||||
text-rendering : optimizeLegibility;
|
||||
margin : 0;
|
||||
padding : 0;
|
||||
height : 100%;
|
||||
}
|
||||
|
||||
.admin{
|
||||
|
||||
header{
|
||||
background-color : @red;
|
||||
font-size: 2em;
|
||||
padding : 20px 0px;
|
||||
color : white;
|
||||
margin-bottom: 30px;
|
||||
i{
|
||||
margin-right: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@import 'naturalcrit/styles/core.less';
|
||||
html,body, #reactRoot{
|
||||
min-height : 100%;
|
||||
}
|
||||
body{
|
||||
height : 100%;
|
||||
margin : 0;
|
||||
padding : 0;
|
||||
background-color : #ddd;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
font-weight : 100;
|
||||
color : #4b5055;
|
||||
text-rendering : optimizeLegibility;
|
||||
}
|
||||
.admin {
|
||||
nav {
|
||||
background-color : @red;
|
||||
.navItem{
|
||||
background-color : @red;
|
||||
}
|
||||
.homebreweryLogo{
|
||||
font-family : CodeBold;
|
||||
font-size : 12px;
|
||||
color : white;
|
||||
div{
|
||||
margin-top : 2px;
|
||||
margin-bottom : -2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
h1{
|
||||
margin-bottom : 10px;
|
||||
font-size : 2em;
|
||||
font-weight : 800;
|
||||
border-bottom : 1px solid #ddd;
|
||||
}
|
||||
main.content{
|
||||
width : 1000px;
|
||||
margin : 0 auto;
|
||||
padding : 50px 20px;
|
||||
background-color : white;
|
||||
.dangerZone{
|
||||
margin : 30px 0px;
|
||||
padding : 10px 20px;
|
||||
background : repeating-linear-gradient(45deg, @yellow, @yellow 10px, darken(#333, 10%) 10px, darken(#333, 10%) 20px);
|
||||
font-size : 1em;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
client/admin/adminSearch/adminSearch.jsx
Normal file
86
client/admin/adminSearch/adminSearch.jsx
Normal file
@@ -0,0 +1,86 @@
|
||||
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const request = require('superagent');
|
||||
|
||||
const BrewTable = require('../brewTable/brewTable.jsx');
|
||||
|
||||
const LIMIT = 10;
|
||||
|
||||
const AdminSearch = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
adminKey : '',
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
totalBrews : 1,
|
||||
brews: [],
|
||||
|
||||
searching : false,
|
||||
error : null,
|
||||
|
||||
page : 1,
|
||||
searchTerms : ''
|
||||
};
|
||||
},
|
||||
|
||||
handleSearch : function(e){
|
||||
this.setState({
|
||||
searchTerms : e.target.value
|
||||
});
|
||||
},
|
||||
handlePage : function(e){
|
||||
this.setState({
|
||||
page : e.target.value
|
||||
});
|
||||
},
|
||||
|
||||
search : function(){
|
||||
this.setState({ searching : true, error : null });
|
||||
|
||||
request.get(`/api/brew`)
|
||||
.query({
|
||||
terms : this.state.searchTerms,
|
||||
limit : LIMIT,
|
||||
page : this.state.page - 1
|
||||
})
|
||||
.set('x-homebrew-admin', this.props.adminKey)
|
||||
.end((err, res) => {
|
||||
if(err){
|
||||
this.setState({
|
||||
searching : false,
|
||||
error : err && err.toString()
|
||||
});
|
||||
}else{
|
||||
this.setState({
|
||||
brews : res.body.brews,
|
||||
totalBrews : res.body.total
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function(){
|
||||
return <div className='adminSearch'>
|
||||
<h1>Admin Search</h1>
|
||||
<div className='controls'>
|
||||
<input className='search' type='text' value={this.state.searchTerms} onChange={this.handleSearch} />
|
||||
|
||||
<button onClick={this.search}> <i className='fa fa-search' /> search </button>
|
||||
|
||||
|
||||
<div className='page'>
|
||||
page:
|
||||
<input type='text' value={this.state.page} onChange={this.handlePage} />
|
||||
/ {Math.ceil(this.state.totalBrews / LIMIT)}
|
||||
</div>
|
||||
</div>
|
||||
<BrewTable brews={this.state.brews} />
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = AdminSearch;
|
||||
17
client/admin/adminSearch/adminSearch.less
Normal file
17
client/admin/adminSearch/adminSearch.less
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
.adminSearch{
|
||||
.controls{
|
||||
margin-bottom : 20px;
|
||||
input.search{
|
||||
height : 33px;
|
||||
padding : 10px;
|
||||
}
|
||||
.page {
|
||||
float : right;
|
||||
font-weight : 800;
|
||||
input{
|
||||
width : 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
client/admin/brewLookup/brewLookup.jsx
Normal file
86
client/admin/brewLookup/brewLookup.jsx
Normal file
@@ -0,0 +1,86 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const request = require('superagent');
|
||||
const BrewTable = require('../brewTable/brewTable.jsx');
|
||||
|
||||
const BrewLookup = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
adminKey : '',
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
query:'',
|
||||
resultBrew : null,
|
||||
searching : false,
|
||||
error : null
|
||||
};
|
||||
},
|
||||
|
||||
handleChange : function(e){
|
||||
this.setState({
|
||||
query : e.target.value
|
||||
})
|
||||
},
|
||||
lookup : function(){
|
||||
this.setState({ searching : true, error : null });
|
||||
|
||||
request.get(`/admin/lookup/${this.state.query}`)
|
||||
.set('x-homebrew-admin', this.props.adminKey)
|
||||
.end((err, res) => {
|
||||
this.setState({
|
||||
searching : false,
|
||||
error : err && err.toString(),
|
||||
resultBrew : (err ? null : res.body)
|
||||
});
|
||||
})
|
||||
},
|
||||
|
||||
renderFoundBrew : function(){
|
||||
if(this.state.searching) return <div className='searching'><i className='fa fa-spin fa-spinner' /></div>;
|
||||
if(!this.state.resultBrew) return <div className='noBrew'>No brew found.</div>;
|
||||
|
||||
return <BrewTable brews={[this.state.resultBrew ]} />
|
||||
|
||||
/*
|
||||
const brew = this.state.resultBrew;
|
||||
return <div className='brewRow'>
|
||||
<div>{brew.title}</div>
|
||||
<div>{brew.authors.join(', ')}</div>
|
||||
<div><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></div>
|
||||
<div><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></div>
|
||||
<div>{Moment(brew.updatedAt).fromNow()}</div>
|
||||
<div>{brew.views}</div>
|
||||
<div>
|
||||
<div className='deleteButton'>
|
||||
<i className='fa fa-trash' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
*/
|
||||
},
|
||||
|
||||
renderError : function(){
|
||||
if(!this.state.error) return;
|
||||
|
||||
return <div className='error'>
|
||||
{this.state.error}
|
||||
</div>
|
||||
},
|
||||
|
||||
render: function(){
|
||||
return <div className='brewLookup'>
|
||||
<h1>Brew Lookup</h1>
|
||||
<input type='text' value={this.state.query} onChange={this.handleChange} placeholder='edit or share id...' />
|
||||
<button onClick={this.lookup}><i className='fa fa-search'/></button>
|
||||
|
||||
{this.renderFoundBrew()}
|
||||
{this.renderError()}
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = BrewLookup;
|
||||
13
client/admin/brewLookup/brewLookup.less
Normal file
13
client/admin/brewLookup/brewLookup.less
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
.brewLookup{
|
||||
height : 200px;
|
||||
input{
|
||||
height : 33px;
|
||||
margin-bottom : 20px;
|
||||
padding : 0px 10px;
|
||||
}
|
||||
.error{
|
||||
font-weight : 800;
|
||||
color : @red;
|
||||
}
|
||||
}
|
||||
54
client/admin/brewTable/brewTable.jsx
Normal file
54
client/admin/brewTable/brewTable.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Moment = require('moment');
|
||||
|
||||
//TODO: Add in delete
|
||||
|
||||
const BrewTable = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brews : []
|
||||
};
|
||||
},
|
||||
renderRows : function(){
|
||||
return _.map(this.props.brews, (brew) => {
|
||||
let authors = 'None.';
|
||||
if(brew.authors && brew.authors.length) authors = brew.authors.join(', ');
|
||||
|
||||
return <tr className={cx('brewRow', {'isEmpty' : brew.text == "false"})} key={brew.shareId || brew}>
|
||||
<td>{brew.title}</td>
|
||||
<td>{authors}</td>
|
||||
<td><a href={'/edit/' + brew.editId} target='_blank'>{brew.editId}</a></td>
|
||||
<td><a href={'/share/' + brew.shareId} target='_blank'>{brew.shareId}</a></td>
|
||||
<td>{Moment(brew.updatedAt).fromNow()}</td>
|
||||
<td>{brew.views}</td>
|
||||
<td className='deleteButton'>
|
||||
<i className='fa fa-trash' />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
});
|
||||
},
|
||||
render: function(){
|
||||
return <table className='brewTable'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Authors</th>
|
||||
<th>Edit Link</th>
|
||||
<th>Share Link</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Views</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.renderRows()}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = BrewTable;
|
||||
44
client/admin/brewTable/brewTable.less
Normal file
44
client/admin/brewTable/brewTable.less
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
table.brewTable{
|
||||
th{
|
||||
padding : 10px;
|
||||
background-color : fade(@blue, 20%);
|
||||
font-weight : 800;
|
||||
}
|
||||
tr:nth-child(even){
|
||||
background-color : fade(@green, 10%);
|
||||
}
|
||||
tr.isEmpty{
|
||||
background-color : fade(@red, 30%);
|
||||
}
|
||||
td{
|
||||
min-width : 100px;
|
||||
padding : 10px;
|
||||
text-align : center;
|
||||
|
||||
/*
|
||||
&.preview{
|
||||
position : relative;
|
||||
&:hover{
|
||||
.content{
|
||||
display : block;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
position : absolute;
|
||||
display : none;
|
||||
top : 100%;
|
||||
left : 0px;
|
||||
z-index : 1000;
|
||||
max-height : 500px;
|
||||
width : 300px;
|
||||
padding : 30px;
|
||||
background-color : white;
|
||||
font-family : monospace;
|
||||
text-align : left;
|
||||
pointer-events : none;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var Moment = require('moment')
|
||||
|
||||
var HomebrewAdmin = React.createClass({
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
homebrews : [],
|
||||
admin_key : ''
|
||||
};
|
||||
},
|
||||
|
||||
renderBrews : function(){
|
||||
return _.map(this.props.homebrews, (brew)=>{
|
||||
return <tr className={cx('brewRow', {'isEmpty' : brew.text == ""})} key={brew.sharedId}>
|
||||
|
||||
<td>{brew.editId}</td>
|
||||
<td>{brew.shareId}</td>
|
||||
<td>{Moment(brew.createdAt).fromNow()}</td>
|
||||
<td>{Moment(brew.updatedAt).fromNow()}</td>
|
||||
<td>{Moment(brew.lastViewed).fromNow()}</td>
|
||||
<td>{brew.views}</td>
|
||||
|
||||
<td className='preview'>
|
||||
<a target="_blank" href={'/homebrew/share/' + brew.shareId}>view</a>
|
||||
<div className='content'>
|
||||
{brew.text.slice(0, 500)}
|
||||
</div>
|
||||
</td>
|
||||
<td><a href={'/homebrew/remove/' + brew.editId +'?admin_key=' + this.props.admin_key}><i className='fa fa-trash' /></a></td>
|
||||
</tr>
|
||||
|
||||
})
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className='homebrewAdmin'>
|
||||
<h2>Homebrews - {this.props.homebrews.length}</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Edit Id</th>
|
||||
<th>Share Id</th>
|
||||
<th>Created At</th>
|
||||
<th>Last Updated</th>
|
||||
<th>Last Viewed</th>
|
||||
<th>Number of Views</th>
|
||||
<th>Preview</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.renderBrews()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = HomebrewAdmin;
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
.homebrewAdmin{
|
||||
table{
|
||||
overflow-y : scroll;
|
||||
max-height : 800px;
|
||||
th{
|
||||
padding : 10px;
|
||||
font-weight : 800;
|
||||
}
|
||||
tr:nth-child(even){
|
||||
background-color : fade(@green, 10%);
|
||||
}
|
||||
tr.isEmpty{
|
||||
background-color : fade(@red, 30%);
|
||||
}
|
||||
td{
|
||||
min-width : 100px;
|
||||
padding : 10px;
|
||||
text-align : center;
|
||||
&.preview{
|
||||
position : relative;
|
||||
&:hover{
|
||||
.content{
|
||||
display : block;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
position : absolute;
|
||||
display : none;
|
||||
top : 100%;
|
||||
left : 0px;
|
||||
z-index : 1000;
|
||||
max-height : 500px;
|
||||
width : 300px;
|
||||
padding : 30px;
|
||||
background-color : white;
|
||||
font-family : monospace;
|
||||
text-align : left;
|
||||
pointer-events : none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
client/admin/invalidBrew/invalidBrew.jsx
Normal file
54
client/admin/invalidBrew/invalidBrew.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const request = require('superagent');
|
||||
const BrewTable = require('../brewTable/brewTable.jsx');
|
||||
|
||||
|
||||
const InvalidBrew = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
adminKey : '',
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
brews: []
|
||||
};
|
||||
},
|
||||
getInvalid : function(){
|
||||
request.get(`/admin/invalid`)
|
||||
.set('x-homebrew-admin', this.props.adminKey)
|
||||
.end((err, res) => {
|
||||
this.setState({
|
||||
brews : res.body
|
||||
});
|
||||
})
|
||||
},
|
||||
removeInvalid : function(){
|
||||
if(!this.state.brews.length) return;
|
||||
if(!confirm(`Are you sure you want to remove ${this.state.brews.length} brews`)) return;
|
||||
if(!confirm('Sure you are sure?')) return;
|
||||
|
||||
request.delete(`/admin/invalid`)
|
||||
.set('x-homebrew-admin', this.props.adminKey)
|
||||
.end((err, res) => {
|
||||
console.log(err, res.body);
|
||||
alert('Invalid brews removed!');
|
||||
this.getInvalid();
|
||||
})
|
||||
},
|
||||
render: function(){
|
||||
return <div className='invalidBrew'>
|
||||
<h1>Remove Invalid Brews</h1>
|
||||
<div>This will removes all brews older than 3 days and shorter than a tweet.</div>
|
||||
<button className='get' onClick={this.getInvalid}> Get Invalid Brews</button>
|
||||
<button className='remove' disabled={this.state.brews.length == 0} onClick={this.removeInvalid}> Remove invalid Brews</button>
|
||||
|
||||
<BrewTable brews={this.state.brews} />
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = InvalidBrew;
|
||||
5
client/admin/invalidBrew/invalidBrew.less
Normal file
5
client/admin/invalidBrew/invalidBrew.less
Normal file
@@ -0,0 +1,5 @@
|
||||
.invalidBrew{
|
||||
button{
|
||||
margin: 10px 4px;
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var Statusbar = require('../statusbar/statusbar.jsx');
|
||||
var PageContainer = require('../pageContainer/pageContainer.jsx');
|
||||
var Editor = require('../editor/editor.jsx');
|
||||
|
||||
var FullClassGen = require('../editor/snippets/fullclass.gen.js');
|
||||
|
||||
var request = require("superagent");
|
||||
|
||||
var SAVE_TIMEOUT = 3000;
|
||||
|
||||
var EditPage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
id : null,
|
||||
entry : {
|
||||
text : "",
|
||||
shareId : null,
|
||||
editId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
text: this.props.entry.text,
|
||||
pending : false,
|
||||
lastUpdated : this.props.entry.updatedAt
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var self = this;
|
||||
window.onbeforeunload = function(){
|
||||
if(!self.state.pending) return;
|
||||
return "You have unsaved changes!";
|
||||
}
|
||||
},
|
||||
|
||||
handleTextChange : function(text){
|
||||
this.setState({
|
||||
text : text,
|
||||
pending : true
|
||||
});
|
||||
this.save();
|
||||
},
|
||||
|
||||
save : _.debounce(function(){
|
||||
request
|
||||
.put('/homebrew/update/' + this.props.id)
|
||||
.send({text : this.state.text})
|
||||
.end((err, res) => {
|
||||
this.setState({
|
||||
pending : false,
|
||||
lastUpdated : res.body.updatedAt
|
||||
})
|
||||
})
|
||||
}, SAVE_TIMEOUT),
|
||||
|
||||
render : function(){
|
||||
return <div className='editPage'>
|
||||
<Statusbar
|
||||
editId={this.props.entry.editId}
|
||||
shareId={this.props.entry.shareId}
|
||||
printId={this.props.entry.shareId}
|
||||
lastUpdated={this.state.lastUpdated}
|
||||
isPending={this.state.pending} />
|
||||
|
||||
<div className='paneSplit'>
|
||||
<div className='leftPane'>
|
||||
<Editor text={this.state.text} onChange={this.handleTextChange} />
|
||||
</div>
|
||||
<div className='rightPane'>
|
||||
<PageContainer text={this.state.text} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = EditPage;
|
||||
@@ -1,57 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
var SnippetIcons = require('./snippets/snippets.js');
|
||||
|
||||
|
||||
var Editor = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
text : "",
|
||||
onChange : function(){}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.refs.textarea.focus();
|
||||
},
|
||||
|
||||
handleTextChange : function(e){
|
||||
this.props.onChange(e.target.value);
|
||||
},
|
||||
|
||||
iconClick : function(snippetFn){
|
||||
var curPos = this.refs.textarea.selectionStart;
|
||||
this.props.onChange(this.props.text.slice(0, curPos) +
|
||||
snippetFn() +
|
||||
this.props.text.slice(curPos + 1));
|
||||
},
|
||||
|
||||
renderTemplateIcons : function(){
|
||||
return _.map(SnippetIcons, (t) => {
|
||||
return <div className='icon' key={t.icon}
|
||||
onClick={this.iconClick.bind(this, t.snippet)}
|
||||
data-tooltip={t.tooltip}>
|
||||
<i className={'fa ' + t.icon} />
|
||||
</div>;
|
||||
})
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className='editor'>
|
||||
<div className='textIcons'>
|
||||
{this.renderTemplateIcons()}
|
||||
</div>
|
||||
<textarea
|
||||
ref='textarea'
|
||||
value={this.props.text}
|
||||
onChange={this.handleTextChange} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Editor;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
|
||||
.editor{
|
||||
position : relative;
|
||||
height : 100%;
|
||||
min-height : 100%;
|
||||
width : 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.textIcons{
|
||||
display : inline-block;
|
||||
vertical-align : top;
|
||||
.icon{
|
||||
display : inline-block;
|
||||
height : 30px;
|
||||
width : 30px;
|
||||
cursor : pointer;
|
||||
font-size : 1.5em;
|
||||
line-height : 30px;
|
||||
text-align : center;
|
||||
&:nth-child(8n + 1){ background-color: @blue; }
|
||||
&:nth-child(8n + 2){ background-color: @orange; }
|
||||
&:nth-child(8n + 3){ background-color: @teal; }
|
||||
&:nth-child(8n + 4){ background-color: @red; }
|
||||
&:nth-child(8n + 5){ background-color: @purple; }
|
||||
&:nth-child(8n + 6){ background-color: @silver; }
|
||||
&:nth-child(8n + 7){ background-color: @yellow; }
|
||||
&:nth-child(8n + 8){ background-color: @green; }
|
||||
}
|
||||
}
|
||||
textarea{
|
||||
box-sizing : border-box;
|
||||
resize : none;
|
||||
overflow-y : scroll;
|
||||
height : 100%;
|
||||
width : 100%;
|
||||
padding : 10px;
|
||||
border : none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function(classname){
|
||||
|
||||
classname = classname || _.sample(['Archivist', 'Fancyman', 'Linguist', 'Fletcher',
|
||||
'Notary', 'Berserker-Typist', 'Fishmongerer', 'Manicurist', 'Haberdasher', 'Concierge'])
|
||||
|
||||
var features = [
|
||||
"Astrological Botany",
|
||||
"Astrological Chemistry",
|
||||
"Biochemical Sorcery",
|
||||
"Civil Alchemy",
|
||||
"Consecrated Biochemistry",
|
||||
"Demonic Anthropology",
|
||||
"Divinatory Mineralogy",
|
||||
"Genetic Banishing",
|
||||
"Hermetic Geography",
|
||||
"Immunological Incantations",
|
||||
"Nuclear Illusionism",
|
||||
"Ritual Astronomy",
|
||||
"Seismological Divination",
|
||||
"Spiritual Biochemistry",
|
||||
"Statistical Occultism",
|
||||
"Police Necromancer",
|
||||
"Sixgun Poisoner",
|
||||
"Pharmaceutical Gunslinger",
|
||||
"Infernal Banker",
|
||||
"Spell Analyst",
|
||||
"Gunslinger Corruptor",
|
||||
"Torque Interfacer",
|
||||
"Exo Interfacer",
|
||||
"Gunpowder Torturer",
|
||||
"Orbital Gravedigger",
|
||||
"Phased Linguist",
|
||||
"Mathematical Pharmacist",
|
||||
"Plasma Outlaw",
|
||||
"Malefic Chemist",
|
||||
"Police Cultist"
|
||||
];
|
||||
|
||||
var maxes = [4,3,3,3,3,2,2,1,1]
|
||||
var drawSlots = function(Slots){
|
||||
var slots = Number(Slots);
|
||||
return _.times(9, function(i){
|
||||
var max = maxes[i];
|
||||
if(slots < 1) return "—";
|
||||
var res = _.min([max, slots]);
|
||||
slots -= res;
|
||||
return res;
|
||||
}).join(' | ')
|
||||
}
|
||||
|
||||
var extraWide = (_.random(0,1) === 0) ? "" : "___\n";
|
||||
|
||||
var cantrips = 3;
|
||||
var spells = 1;
|
||||
var slots = 2;
|
||||
return "##### The " + classname + "\n" +
|
||||
"___\n" + extraWide +
|
||||
"| Level | Proficiency Bonus | Features | Cantrips Known | Spells Known | 1st | 2nd | 3rd | 4th | 5th | 6th | 7th | 8th | 9th |\n"+
|
||||
"|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|\n" +
|
||||
_.map(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", "11th", "12th", "13th", "14th", "15th", "16th", "17th", "18th", "19th", "20th"],function(levelName, level){
|
||||
var res = [
|
||||
levelName,
|
||||
"+" + Math.ceil(level/5 + 1),
|
||||
_.sample(features, _.sample([0,1,1])).join(', ') || "Ability Score Improvement",
|
||||
cantrips,
|
||||
spells,
|
||||
drawSlots(slots)
|
||||
].join(' | ');
|
||||
|
||||
cantrips += _.random(0,1);
|
||||
spells += _.random(0,1);
|
||||
slots += _.random(0,2);
|
||||
|
||||
return "| " + res + " |";
|
||||
}).join('\n') +'\n\n';
|
||||
|
||||
};
|
||||
@@ -1,168 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
var genList = function(list, max){
|
||||
return _.sample(list, _.random(0,max)).join(', ') || "None";
|
||||
}
|
||||
|
||||
module.exports = function(){
|
||||
|
||||
var monsterName = _.sample([
|
||||
"All-devouring Baseball Imp",
|
||||
"All-devouring Gumdrop Wraith",
|
||||
"Chocolate Hydra",
|
||||
"Devouring Peacock",
|
||||
"Economy-sized Colossus of the Lemonade Stand",
|
||||
"Ghost Pigeon",
|
||||
"Gibbering Duck",
|
||||
"Sparklemuffin Peacock Spider",
|
||||
"Gum Elemental",
|
||||
"Illiterate Construct of the Candy Store",
|
||||
"Ineffable Chihuahua",
|
||||
"Irritating Death Hamster",
|
||||
"Irritating Gold Mouse",
|
||||
"Juggernaut Snail",
|
||||
"Juggernaut of the Sock Drawer",
|
||||
"Koala of the Cosmos",
|
||||
"Mad Koala of the West",
|
||||
"Milk Djinni of the Lemonade Stand",
|
||||
"Mind Ferret",
|
||||
"Mystic Salt Spider",
|
||||
"Necrotic Halitosis Angel",
|
||||
"Pinstriped Famine Sheep",
|
||||
"Ritalin Leech",
|
||||
"Shocker Kangaroo",
|
||||
"Stellar Tennis Juggernaut",
|
||||
"Wailing Quail of the Sun",
|
||||
"Angel Pigeon",
|
||||
"Anime Sphinx",
|
||||
"Bored Avalanche Sheep of the Wasteland",
|
||||
"Devouring Nougat Sphinx of the Sock Drawer",
|
||||
"Djinni of the Footlocker",
|
||||
"Ectoplasmic Jazz Devil",
|
||||
"Flatuent Angel",
|
||||
"Gelatinous Duck of the Dream-Lands",
|
||||
"Gelatinous Mouse",
|
||||
"Golem of the Footlocker",
|
||||
"Lich Wombat",
|
||||
"Mechanical Sloth of the Past",
|
||||
"Milkshake Succubus",
|
||||
"Puffy Bone Peacock of the East",
|
||||
"Rainbow Manatee",
|
||||
"Rune Parrot",
|
||||
"Sand Cow",
|
||||
"Sinister Vanilla Dragon",
|
||||
"Snail of the North",
|
||||
"Spider of the Sewer",
|
||||
"Stellar Sawdust Leech",
|
||||
"Storm Anteater of Hell",
|
||||
"Stupid Spirit of the Brewery",
|
||||
"Time Kangaroo",
|
||||
"Tomb Poodle",
|
||||
]);
|
||||
|
||||
var type = _.sample(['Tiny', 'Small', 'Medium', 'Large', 'Gargantuan', 'Stupidly vast']) + " " + _.sample(['beast', 'fiend', 'annoyance', 'guy', 'cutie'])
|
||||
|
||||
var alignment =_.sample([
|
||||
"annoying evil",
|
||||
"chaotic gossipy",
|
||||
"chaotic sloppy",
|
||||
"depressed neutral",
|
||||
"lawful bogus",
|
||||
"lawful coy",
|
||||
"manic-depressive evil",
|
||||
"narrow-minded neutral",
|
||||
"neutral annoying",
|
||||
"neutral ignorant",
|
||||
"oedpipal neutral",
|
||||
"silly neutral",
|
||||
"unoriginal neutral",
|
||||
"weird neutral",
|
||||
"wordy evil",
|
||||
"unaligned"
|
||||
]);
|
||||
|
||||
|
||||
|
||||
var stats = '>|' + _.times(6, function(){
|
||||
var num = _.random(1,20);
|
||||
var mod = Math.ceil(num/2 - 5)
|
||||
return num + " (" + (mod >= 0 ? '+'+mod : mod ) + ")"
|
||||
}).join('|') + '|';
|
||||
|
||||
|
||||
var genAbilities = function(){
|
||||
return _.sample([
|
||||
"> ***Pack Tactics.*** These guys work together. Like super well, you don't even know.",
|
||||
"> ***False Appearance. *** While the armor reamin motionless, it is indistinguishable from a normal suit of armor.",
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
var genAction = function(){
|
||||
var name = _.sample([
|
||||
"Abdominal Drop",
|
||||
"Airplane Hammer",
|
||||
"Atomic Death Throw",
|
||||
"Bulldog Rake",
|
||||
"Corkscrew Strike",
|
||||
"Crossed Splash",
|
||||
"Crossface Suplex",
|
||||
"DDT Powerbomb",
|
||||
"Dual Cobra Wristlock",
|
||||
"Dual Throw",
|
||||
"Elbow Hold",
|
||||
"Gory Body Sweep",
|
||||
"Heel Jawbreaker",
|
||||
"Jumping Driver",
|
||||
"Open Chin Choke",
|
||||
"Scorpion Flurry",
|
||||
"Somersault Stump Fists",
|
||||
"Suffering Wringer",
|
||||
"Super Hip Submission",
|
||||
"Super Spin",
|
||||
"Team Elbow",
|
||||
"Team Foot",
|
||||
"Tilt-a-whirl Chin Sleeper",
|
||||
"Tilt-a-whirl Eye Takedown",
|
||||
"Turnbuckle Roll"
|
||||
])
|
||||
|
||||
return "> ***" + name + ".*** *Melee Weapon Attack:* +4 to hit, reach 5ft., one target. *Hit* 5 (1d6 + 2) ";
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
"___",
|
||||
"> ## " + monsterName,
|
||||
">*" + type + ", " + alignment+ "*",
|
||||
"> ___",
|
||||
"> - **Armor Class** " + _.random(10,20),
|
||||
"> - **Hit Points** " + _.random(1, 150) + "(1d4 + 5)",
|
||||
"> - **Speed** " + _.random(0,50) + "ft.",
|
||||
">___",
|
||||
">|STR|DEX|CON|INT|WIS|CHA|",
|
||||
">|:---:|:---:|:---:|:---:|:---:|:---:|",
|
||||
stats,
|
||||
">___",
|
||||
"> - **Condition Immunities** " + genList(["groggy", "swagged", "weak-kneed", "buzzed", "groovy", "melancholy", "drunk"], 3),
|
||||
"> - **Senses** passive Perception " + _.random(3, 20),
|
||||
"> - **Languages** " + genList(["Common", "Pottymouth", "Gibberish", "Latin", "Jive"], 2),
|
||||
"> - **Challenge** " + _.random(0, 15) + " (" + _.random(10,10000)+ " XP)",
|
||||
"> ___",
|
||||
_.times(_.random(0,2), function(){
|
||||
return genAbilities()
|
||||
}).join('\n>\n'),
|
||||
"> ### Actions",
|
||||
_.times(_.random(1,2), function(){
|
||||
return genAction()
|
||||
}).join('\n>\n'),
|
||||
].join('\n') + '\n\n\n';
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
@@ -1,109 +0,0 @@
|
||||
var SpellGen = require('./spell.gen.js');
|
||||
var ClassTableGen = require('./classtable.gen.js');
|
||||
var MonsterBlockGen = require('./monsterblock.gen.js');
|
||||
var ClassFeatureGen = require('./classfeature.gen.js');
|
||||
var FullClassGen = require('./fullclass.gen.js');
|
||||
|
||||
module.exports = [
|
||||
/*
|
||||
{
|
||||
tooltip : 'Full Class',
|
||||
icon : 'fa-user',
|
||||
snippet : FullClassGen,
|
||||
},
|
||||
*/
|
||||
{
|
||||
tooltip : 'Spell',
|
||||
icon : 'fa-magic',
|
||||
snippet : SpellGen,
|
||||
},
|
||||
{
|
||||
tooltip : 'Class Feature',
|
||||
icon : 'fa-trophy',
|
||||
snippet : ClassFeatureGen,
|
||||
},
|
||||
{
|
||||
tooltip : 'Note',
|
||||
icon : 'fa-sticky-note',
|
||||
snippet : function(){
|
||||
return [
|
||||
"> ##### Time to Drop Knowledge",
|
||||
"> Use notes to point out some interesting information. ",
|
||||
"> ",
|
||||
"> **Tables and lists** both work within a note."
|
||||
].join('\n');
|
||||
},
|
||||
},
|
||||
{
|
||||
tooltip : 'Table',
|
||||
icon : 'fa-list',
|
||||
snippet : function(){
|
||||
return [
|
||||
"##### Cookie Tastiness",
|
||||
"| Tastiness | Cookie Type |",
|
||||
"|:----:|:-------------|",
|
||||
"| -5 | Raisin |",
|
||||
"| 8th | Chocolate Chip |",
|
||||
"| 11th | 2 or lower |",
|
||||
"| 14th | 3 or lower |",
|
||||
"| 17th | 4 or lower |\n\n",
|
||||
].join('\n');
|
||||
},
|
||||
},
|
||||
{
|
||||
tooltip : 'Monster Stat Block',
|
||||
icon : 'fa-bug',
|
||||
snippet : MonsterBlockGen,
|
||||
},
|
||||
{
|
||||
tooltip : "Class Table",
|
||||
icon : 'fa-table',
|
||||
snippet : ClassTableGen,
|
||||
},
|
||||
{
|
||||
tooltip : "Column Break",
|
||||
icon : 'fa-columns',
|
||||
snippet : function(){
|
||||
return "```\n```\n\n";
|
||||
}
|
||||
},
|
||||
{
|
||||
tooltip : "New Page",
|
||||
icon : 'fa-file-text',
|
||||
snippet : function(){
|
||||
return "\\page\n\n";
|
||||
}
|
||||
},
|
||||
{
|
||||
tooltip : "Vertical Spacing",
|
||||
icon : 'fa-arrows-v',
|
||||
snippet : function(){
|
||||
return "<div style='margin-top:140px'></div>\n\n";
|
||||
}
|
||||
},
|
||||
{
|
||||
tooltip : "Insert Image",
|
||||
icon : 'fa-image',
|
||||
snippet : function(){
|
||||
return "<img src='https://i.imgur.com/RJ6S6eY.gif' style='position:absolute;bottom:-10px;right:-60px;' />";
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
tooltip : "Page number & Footnote",
|
||||
icon : 'fa-book',
|
||||
snippet : function(){
|
||||
return "<div class='pageNumber'>1</div>\n<div class='footnote'>PART 1 | FANCINESS</div>\n\n";
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
tooltip : "Ink Friendly",
|
||||
icon : 'fa-print',
|
||||
snippet : function(){
|
||||
return "<style>\n .phb{ background : white;}\n .phb img{ display : none;}\n .phb hr+blockquote{background : white;}\n</style>\n\n";
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
]
|
||||
@@ -1,78 +0,0 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
module.exports = function(){
|
||||
|
||||
|
||||
var spellNames = [
|
||||
"Astral Rite of Acne",
|
||||
"Create Acne",
|
||||
"Cursed Ramen Erruption",
|
||||
"Dark Chant of the Dentists",
|
||||
"Erruption of Immaturity",
|
||||
"Flaming Disc of Inconvenience",
|
||||
"Heal Bad Hygene",
|
||||
"Heavenly Transfiguration of the Cream Devil",
|
||||
"Hellish Cage of Mucus",
|
||||
"Irritate Peanut Butter Fairy",
|
||||
"Luminous Erruption of Tea",
|
||||
"Mystic Spell of the Poser",
|
||||
"Sorcerous Enchantment of the Chimneysweep",
|
||||
"Steak Sauce Ray",
|
||||
"Talk to Groupie",
|
||||
"Astonishing Chant of Chocolate",
|
||||
"Astounding Pasta Puddle",
|
||||
"Ball of Annoyance",
|
||||
"Cage of Yarn",
|
||||
"Control Noodles Elemental",
|
||||
"Create Nervousness",
|
||||
"Cure Baldness",
|
||||
"Cursed Ritual of Bad Hair",
|
||||
"Dispell Piles in Dentist",
|
||||
"Eliminate Florists",
|
||||
"Illusionary Transfiguration of the Babysitter",
|
||||
"Necromantic Armor of Salad Dressing",
|
||||
"Occult Transfiguration of Foot Fetish",
|
||||
"Protection from Mucus Giant",
|
||||
"Tinsel Blast",
|
||||
"Alchemical Evocation of the Goths",
|
||||
"Call Fangirl",
|
||||
"Divine Spell of Crossdressing",
|
||||
"Dominate Ramen Giant",
|
||||
"Eliminate Vindictiveness in Gym Teacher",
|
||||
"Extra-Planar Spell of Irritation",
|
||||
"Induce Whining in Babysitter",
|
||||
"Invoke Complaining",
|
||||
"Magical Enchantment of Arrogance",
|
||||
"Occult Globe of Salad Dressing",
|
||||
"Overwhelming Enchantment of the Chocolate Fairy",
|
||||
"Sorcerous Dandruff Globe",
|
||||
"Spiritual Invocation of the Costumers",
|
||||
"Ultimate Rite of the Confetti Angel",
|
||||
"Ultimate Ritual of Mouthwash",
|
||||
];
|
||||
|
||||
|
||||
var level = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th"];
|
||||
var spellSchools = ["abjuration", "conjuration", "divination", "enchantment", "evocation", "illusion", "necromancy", "transmutation"];
|
||||
|
||||
|
||||
var components = _.sample(["V", "S", "M"], _.random(1,3)).join(', ');
|
||||
if(components.indexOf("M") !== -1){
|
||||
components += " (" + _.sample(['a small doll', 'a crushed button worth at least 1cp', 'discarded gum wrapper'], _.random(1,3)).join(', ') + ")"
|
||||
}
|
||||
|
||||
return [
|
||||
"#### " + _.sample(spellNames),
|
||||
"*" + _.sample(level) + "-level " + _.sample(spellSchools) + "*",
|
||||
"___",
|
||||
"- **Casting Time:** 1 action",
|
||||
"- **Range:** " + _.sample(["Self", "Touch", "30 feet", "60 feet"]),
|
||||
"- **Components:** " + components,
|
||||
"- **Duration:** " + _.sample(["Until dispelled", "1 round", "Instantaneous", "Concentration, up to 10 minutes", "1 hour"]),
|
||||
"",
|
||||
"A flame, equivalent in brightness to a torch, springs from from an object that you touch. ",
|
||||
"The effect look like a regular flame, but it creates no heat and doesn't use oxygen. ",
|
||||
"A *continual flame* can be covered or hidden but not smothered or quenched.",
|
||||
"\n\n\n"
|
||||
].join('\n');
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,71 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var Statusbar = require('../statusbar/statusbar.jsx');
|
||||
var PageContainer = require('../pageContainer/pageContainer.jsx');
|
||||
var Editor = require('../editor/editor.jsx');
|
||||
|
||||
//var WelcomeText = require('./welcomeMessage.js');
|
||||
|
||||
|
||||
|
||||
var KEY = 'naturalCrit-homebrew';
|
||||
|
||||
var HomePage = React.createClass({
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
welcomeText : ""
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
text: this.props.welcomeText
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
/*
|
||||
var storage = localStorage.getItem(KEY);
|
||||
if(storage){
|
||||
this.setState({
|
||||
text : storage
|
||||
})
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
handleTextChange : function(text){
|
||||
this.setState({
|
||||
text : text
|
||||
});
|
||||
|
||||
//localStorage.setItem(KEY, text);
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return(
|
||||
<div className='homePage'>
|
||||
<Statusbar
|
||||
printId="Nkbh52nx_l"
|
||||
/>
|
||||
<div className='paneSplit'>
|
||||
<div className='leftPane'>
|
||||
<Editor text={this.state.text} onChange={this.handleTextChange} />
|
||||
</div>
|
||||
<div className='rightPane'>
|
||||
<PageContainer text={this.state.text} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href='/homebrew/new' className='floatingNewButton'>
|
||||
Create your own <i className='fa fa-magic' />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = HomePage;
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
.homePage{
|
||||
position : relative;
|
||||
a.floatingNewButton{
|
||||
.animate(background-color);
|
||||
position : absolute;
|
||||
display : block;
|
||||
right : 70px;
|
||||
bottom : 70px;
|
||||
z-index : 100;
|
||||
padding : 1em;
|
||||
background-color : @orange;
|
||||
font-size : 1.5em;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
box-shadow : 3px 3px 15px black;
|
||||
&:hover{
|
||||
background-color : darken(@orange, 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
# The Homebrewery
|
||||
Welcome traveler from an antique land. Please sit and tell us of what you have seen. The unheard of monsters, who slither and bite.
|
||||
|
||||
Tell us of the wondrous items and and artifacts you have found, their mysteries yet to be unlocked. Of the vexing vocations and surprising skills you have seen.
|
||||
|
||||
### Homebrew made easy
|
||||
The Homebrewery allows for the creation and sharing of authentic looking Fifth-Edition homebrews, with just text editing. It accomplishes this by using [Markdown](https://help.github.com/articles/markdown-basics/) along with some custom CSS-styling.
|
||||
|
||||
Stop worrying about learning photoshop, fiddling with spacing, or tracking down the PHB assets. Just focus on making your homebrew **great**.
|
||||
|
||||
**Try it! **Simply edit the text on the left and watch it *update live* on the right.
|
||||
|
||||
|
||||
#### Features
|
||||
* Monster Stat Blocks
|
||||
* Full class tables
|
||||
* Notes and Tables
|
||||
* Images
|
||||
* Vertical spacing, column breaks, and multiple pages
|
||||
|
||||
|
||||
#### Snippets
|
||||
If you aren't used the Markdown-style syntax, don't worry! I've provided several **snippets** at the top of the editor. When clicked, these will *inject* text wherever your text cursor was.
|
||||
|
||||
Each snippet is a common format from the Player's Handbook or is a feature of The Homebrewery. You'll never have to memorize exactly how a Monster Stat Block is suppose to be formatted.
|
||||
|
||||
### Editing and Sharing
|
||||
When you create your own homebrew you will be given a *edit url* and a *share url*. Any changes you make will be automatically saved to the database within a few seconds. Anyone with the edit url will be able to make edits to your homebrew. So be careful about who you share it with.
|
||||
|
||||
Anyone with the *share url* will be able to access a read-only version of your homebrew.
|
||||
|
||||
|
||||
> ##### Words of Caution
|
||||
> ___
|
||||
> * **Concurrent Editing** The Homebrewery does not support concurrent user editing. It's best one user at a time makes edits to avoid overwriting each other.
|
||||
> * **Back-up your brews** I can not guarantee that I will support this project indefinitely. So if you'd like to hang on to your creation be sure to back up it up.
|
||||
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## New Things!
|
||||
What's new in the latest update? Check out the full changelog [here](https://github.com/stolksdorf/NaturalCrit/blob/master/changelog.md)
|
||||
|
||||
* **View Source** on the share page to see the markdown text for the brew
|
||||
* **Fixed Server Issues** should increase stability of the site greatly
|
||||
* **Footnotes & Page Numbers**
|
||||
* **Print View** displays your brew ready for printing, saving to PDF or image.
|
||||
* **Footer Accent** now switches directions each page, neat!
|
||||
* **Standalone Styling** the PHB-style has been reduced to a single file
|
||||
* **Reduced asset sizes** This should help with page load times
|
||||
|
||||
>##### PDF Exporting
|
||||
>The best way to do a PDF export is to use the **print view** of a brew, print that page and save as PDF.
|
||||
>
|
||||
>***"But there's no columns when I do this in Chrome!"***
|
||||
>
|
||||
>This is a known bug in Chrome for **five years**. When saving to PDF, it doesn't respect columns. Amazingly this was just fixed [last month](https://code.google.com/p/chromium/issues/detail?id=99358), but hasn't been deployed yet.
|
||||
>
|
||||
>Converting to PDF *precisely* is **very** difficult. There are many services and libraries out there, but none of them have gotten it right to the level I'm happy with. Most of them use Chrome's engine which has the aforementioned bug in it.
|
||||
>
|
||||
>This is why I made the **print view**. It gives you a single completely standalone HTML version of your brew; Download it, export it, screenshot it, print it, *do whatever you want*.
|
||||
|
||||
|
||||
|
||||
## Bugs, Issues, Suggestions?
|
||||
Have an idea of how to make The Homebrewery better? Or did you find something that wasn't quite right? Head [here](https://github.com/stolksdorf/NaturalCrit/issues/new) and let me know!.
|
||||
|
||||
|
||||
|
||||
|
||||
<img src='http://i.imgur.com/hMna6G0.png' style='position:absolute;bottom:50px;right:30px;width:280px' />
|
||||
|
||||
<div class='pageNumber'>1</div>
|
||||
<div class='footnote'>PART 1 | FANCINESS</div>
|
||||
|
||||
|
||||
|
||||
|
||||
\page
|
||||
|
||||
# Appendix
|
||||
|
||||
### Not quite Markdown
|
||||
Although the Homebrewery uses Markdown, to get all the styling features from the PHB, we had to get a little creative. Some base HTML elements are not used as expected and I've had to include a few new keywords.
|
||||
|
||||
___
|
||||
* **Horizontal Rules** are generally used to *modify* existing elements into a different style. For example, a horizontal rule before a blockquote will give it the style of a Monster Stat Block instead of a note.
|
||||
* **New Pages** are controlled by the author. It's impossible for the site to detect when the end of a page is reached, so indicate you'd like to start a new page, use the new page snippet to get the syntax.
|
||||
* **Code Blocks** are used only to indicate column breaks. Since they don't allow for styling within them, they weren't that useful to use.
|
||||
* **HTML** can be used to get *just* the right look for your homebrew. I've included some examples in the snippet icons above the editor.
|
||||
|
||||
|
||||
|
||||
### Images
|
||||
Images can be included 'inline' with the text using Markdown-style images. However for background images more control is needed.
|
||||
|
||||
Background images should be included as HTML-style img tags. Using inline CSS you can precisely position your image where you'd like it to be. The image **snippet** provides an example of doing this.
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
### Legal Junk
|
||||
You are free to use The Homebrewery is any way that you want, except for claiming that you made it yourself. If you wish to sell or in some way gain profit for what's created on this site, it's your responsibility to ensure you have the proper licenses/rights for any images or resources used.
|
||||
|
||||
|
||||
### Things that don't work
|
||||
There are a few things I couldn't get right
|
||||
|
||||
* Spell save block, with centered text and sans serif are not support. Ran out of mark-up to use
|
||||
* Full page monster stat blocks
|
||||
* "Spell slots per level" text above the levels on a class table.
|
||||
* I built this for Chrome, so if it looks weird to you, use Chrome instead.
|
||||
|
||||
<div class='pageNumber'>2</div>
|
||||
<div class='footnote'>PART 2 | BORING STUFF</div>
|
||||
|
||||
|
||||
|
||||
@@ -1,46 +1,73 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var CreateRouter = require('pico-router').createRouter;
|
||||
|
||||
var HomePage = require('./homePage/homePage.jsx');
|
||||
var EditPage = require('./editPage/editPage.jsx');
|
||||
var SharePage = require('./sharePage/sharePage.jsx');
|
||||
|
||||
var Router;
|
||||
var Homebrew = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
url : "",
|
||||
welcomeText : "",
|
||||
brew : {
|
||||
text : "",
|
||||
shareId : null,
|
||||
editId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
}
|
||||
};
|
||||
},
|
||||
componentWillMount: function() {
|
||||
Router = CreateRouter({
|
||||
'/homebrew/edit/:id' : (args) => {
|
||||
return <EditPage id={args.id} entry={this.props.brew} />
|
||||
},
|
||||
'/homebrew/share/:id' : (args) => {
|
||||
return <SharePage id={args.id} entry={this.props.brew} />
|
||||
},
|
||||
'/homebrew*' : <HomePage welcomeText={this.props.welcomeText} />,
|
||||
});
|
||||
},
|
||||
render : function(){
|
||||
return(
|
||||
<div className='homebrew'>
|
||||
<Router initialUrl={this.props.url}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Homebrew;
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const CreateRouter = require('pico-router').createRouter;
|
||||
const BrewActions = require('homebrewery/brew.actions.js');
|
||||
const AccountActions = require('homebrewery/account.actions.js');
|
||||
|
||||
const HomePage = require('./pages/homePage/homePage.jsx');
|
||||
const EditPage = require('./pages/editPage/editPage.jsx');
|
||||
const UserPage = require('./pages/userPage/userPage.jsx');
|
||||
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
||||
const NewPage = require('./pages/newPage/newPage.jsx');
|
||||
//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
|
||||
const PrintPage = require('./pages/printPage/printPage.jsx');
|
||||
|
||||
let Router;
|
||||
const Homebrew = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
url : '',
|
||||
version : '0.0.0',
|
||||
loginPath : '',
|
||||
|
||||
user : undefined,
|
||||
brew : undefined,
|
||||
brews : []
|
||||
};
|
||||
},
|
||||
componentWillMount: function() {
|
||||
BrewActions.init({
|
||||
version : this.props.version,
|
||||
brew : this.props.brew
|
||||
});
|
||||
AccountActions.init({
|
||||
user : this.props.user,
|
||||
loginPath : this.props.loginPath
|
||||
});
|
||||
|
||||
Router = CreateRouter({
|
||||
'/edit/:id' : <EditPage />,
|
||||
'/share/:id' : <SharePage />,
|
||||
'/user/:username' : (args) => {
|
||||
return <UserPage
|
||||
username={args.username}
|
||||
brews={this.props.brews}
|
||||
/>
|
||||
},
|
||||
'/print/:id' : (args, query) => {
|
||||
return <PrintPage brew={this.props.brew} query={query}/>;
|
||||
},
|
||||
'/print' : (args, query) => {
|
||||
return <PrintPage query={query}/>;
|
||||
},
|
||||
'/new' : <NewPage />,
|
||||
|
||||
|
||||
'/changelog' : <SharePage />,
|
||||
'/test' : <SharePage />,
|
||||
'/test_old' : <SharePage />,
|
||||
|
||||
|
||||
'*' : <HomePage brews={this.props.brews}/>,
|
||||
});
|
||||
},
|
||||
render : function(){
|
||||
return <div className='homebrew'>
|
||||
<Router initialUrl={this.props.url}/>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Homebrew;
|
||||
|
||||
@@ -1,42 +1,16 @@
|
||||
@import 'naturalCrit/styles/reset.less';
|
||||
//@import 'naturalCrit/styles/elements.less';
|
||||
@import 'naturalCrit/styles/animations.less';
|
||||
@import 'naturalCrit/styles/colors.less';
|
||||
@import 'naturalCrit/styles/tooltip.less';
|
||||
|
||||
html,body, #reactContainer{
|
||||
min-height: 100%;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
}
|
||||
|
||||
.homebrew{
|
||||
background-color: @steel;
|
||||
height : 100%;
|
||||
|
||||
.paneSplit{
|
||||
width : 100%;
|
||||
height: 100vh;
|
||||
padding-top: 25px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
.leftPane, .rightPane{
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.leftPane{
|
||||
width : 40%;
|
||||
}
|
||||
.rightPane{
|
||||
width : 60%;
|
||||
height: 100%;
|
||||
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@import 'naturalcrit/styles/core.less';
|
||||
.homebrew{
|
||||
height : 100%;
|
||||
.page{
|
||||
display : flex;
|
||||
height : 100%;
|
||||
background-color : @steel;
|
||||
flex-direction : column;
|
||||
.content{
|
||||
position : relative;
|
||||
height : calc(~"100% - 29px"); //Navbar height
|
||||
flex : auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
client/homebrew/navbar/account.navitem.jsx
Normal file
23
client/homebrew/navbar/account.navitem.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
const React = require('react');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const Store = require('homebrewery/account.store.js');
|
||||
const Actions = require('homebrewery/account.actions.js');
|
||||
|
||||
|
||||
module.exports = function(props){
|
||||
const user = Store.getUser();
|
||||
if(user && user == props.userPage){
|
||||
return <Nav.item onClick={Actions.logout} color='yellow' icon='fa-user-times'>
|
||||
logout
|
||||
</Nav.item>
|
||||
}
|
||||
if(user){
|
||||
return <Nav.item href={`/user/${user}`} color='yellow' icon='fa-user'>
|
||||
{user}
|
||||
</Nav.item>
|
||||
}
|
||||
return <Nav.item onClick={Actions.login} color='teal' icon='fa-sign-in'>
|
||||
login
|
||||
</Nav.item>
|
||||
};
|
||||
9
client/homebrew/navbar/brewTitle.navitem.jsx
Normal file
9
client/homebrew/navbar/brewTitle.navitem.jsx
Normal file
@@ -0,0 +1,9 @@
|
||||
const React = require('react');
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
|
||||
module.exports = Store.createSmartComponent((props) => {
|
||||
return <Nav.item className='brewTitle'>{props.title}</Nav.item>
|
||||
}, (props) => {
|
||||
return {title : Store.getMetaData().title};
|
||||
})
|
||||
@@ -0,0 +1,76 @@
|
||||
const flux = require('pico-flux')
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
const Actions = require('homebrewery/brew.actions.js');
|
||||
|
||||
const onStoreChange = () => {
|
||||
return {
|
||||
status : Store.getStatus(),
|
||||
errors : Store.getErrors()
|
||||
}
|
||||
};
|
||||
|
||||
const ContinousSave = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
status : 'ready',
|
||||
errors : undefined
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
flux.actionEmitter.on('dispatch', this.actionHandler);
|
||||
window.onbeforeunload = ()=>{
|
||||
if(this.props.status !== 'ready') return 'You have unsaved changes!';
|
||||
};
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
flux.actionEmitter.removeListener('dispatch', this.actionHandler);
|
||||
window.onbeforeunload = function(){};
|
||||
},
|
||||
actionHandler : function(actionType){
|
||||
if(actionType == 'UPDATE_BREW_TEXT' || actionType == 'UPDATE_META'){
|
||||
Actions.pendingSave();
|
||||
}
|
||||
},
|
||||
handleClick : function(){
|
||||
Actions.save();
|
||||
},
|
||||
renderError : function(){
|
||||
let errMsg = '';
|
||||
try{
|
||||
errMsg += this.state.errors.toString() + '\n\n';
|
||||
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
||||
}catch(e){}
|
||||
|
||||
return <Nav.item className='continousSave error' icon="fa-warning">
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
|
||||
here
|
||||
</a>.
|
||||
</div>
|
||||
</Nav.item>
|
||||
},
|
||||
render : function(){
|
||||
if(this.props.status == 'error') return this.renderError();
|
||||
|
||||
if(this.props.status == 'saving'){
|
||||
return <Nav.item className='continousSave' icon="fa-spinner fa-spin">saving...</Nav.item>
|
||||
}
|
||||
if(this.props.status == 'pending'){
|
||||
return <Nav.item className='continousSave' onClick={this.handleClick} color='blue' icon='fa-save'>Save Now</Nav.item>
|
||||
}
|
||||
if(this.props.status == 'ready'){
|
||||
return <Nav.item className='continousSave saved'>saved.</Nav.item>
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
module.exports = Store.createSmartComponent(ContinousSave, onStoreChange);
|
||||
79
client/homebrew/navbar/continousSave.navitem.jsx
Normal file
79
client/homebrew/navbar/continousSave.navitem.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
const flux = require('pico-flux')
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
const Actions = require('homebrewery/brew.actions.js');
|
||||
|
||||
const onStoreChange = () => {
|
||||
return {
|
||||
status : Store.getStatus(),
|
||||
errors : Store.getErrors()
|
||||
}
|
||||
};
|
||||
|
||||
const ContinousSave = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
status : 'ready',
|
||||
errors : undefined
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
flux.actionEmitter.on('dispatch', this.actionHandler);
|
||||
window.onbeforeunload = ()=>{
|
||||
if(this.props.status !== 'ready') return 'You have unsaved changes!';
|
||||
};
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
flux.actionEmitter.removeListener('dispatch', this.actionHandler);
|
||||
window.onbeforeunload = function(){};
|
||||
},
|
||||
actionHandler : function(actionType){
|
||||
if(actionType == 'UPDATE_BREW_CODE' || actionType == 'UPDATE_META' || actionType == 'UPDATE_BREW_STYLE'){
|
||||
Actions.pendingSave();
|
||||
}
|
||||
},
|
||||
handleClick : function(){
|
||||
Actions.save();
|
||||
},
|
||||
renderError : function(){
|
||||
let errMsg = '';
|
||||
try{
|
||||
errMsg += this.state.errors.toString() + '\n\n';
|
||||
errMsg += '```\n' + JSON.stringify(this.state.errors.response.error, null, ' ') + '\n```';
|
||||
}catch(e){}
|
||||
|
||||
return <Nav.item className='continousSave error' icon="fa-warning">
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
Looks like there was a problem saving. <br />
|
||||
Back up your brew in a text file, just in case.
|
||||
<br /><br />
|
||||
|
||||
Report the issue <a target='_blank' href={'https://github.com/stolksdorf/naturalcrit/issues/new?body='+ encodeURIComponent(errMsg)}>
|
||||
here
|
||||
</a>.
|
||||
</div>
|
||||
</Nav.item>
|
||||
},
|
||||
render : function(){
|
||||
if(this.props.status == 'error') return this.renderError();
|
||||
|
||||
if(this.props.status == 'saving'){
|
||||
return <Nav.item className='continousSave' icon="fa-spinner fa-spin">saving...</Nav.item>
|
||||
}
|
||||
if(this.props.status == 'pending'){
|
||||
return <Nav.item className='continousSave' onClick={this.handleClick} color='blue' icon='fa-save'>Save Now</Nav.item>
|
||||
}
|
||||
if(this.props.status == 'ready'){
|
||||
return <Nav.item className='continousSave saved'>saved.</Nav.item>
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
module.exports = Store.createSmartComponent(ContinousSave, onStoreChange);
|
||||
33
client/homebrew/navbar/editTitle.navitem.jsx
Normal file
33
client/homebrew/navbar/editTitle.navitem.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const MAX_TITLE_LENGTH = 50;
|
||||
|
||||
|
||||
var EditTitle = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
title : '',
|
||||
onChange : function(){}
|
||||
};
|
||||
},
|
||||
|
||||
handleChange : function(e){
|
||||
if(e.target.value.length > MAX_TITLE_LENGTH) return;
|
||||
this.props.onChange(e.target.value);
|
||||
},
|
||||
render : function(){
|
||||
return <Nav.item className='editTitle'>
|
||||
<input placeholder='Brew Title' type='text' value={this.props.title} onChange={this.handleChange} />
|
||||
|
||||
<div className={cx('charCount', {'max' : this.props.title.length >= MAX_TITLE_LENGTH})}>
|
||||
{this.props.title.length}/{MAX_TITLE_LENGTH}
|
||||
</div>
|
||||
</Nav.item>
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
module.exports = EditTitle;
|
||||
13
client/homebrew/navbar/issue.navitem.jsx
Normal file
13
client/homebrew/navbar/issue.navitem.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
var React = require('react');
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item
|
||||
{...props}
|
||||
newTab={true}
|
||||
href='https://github.com/stolksdorf/homebrewery/issues'
|
||||
color='red'
|
||||
icon='fa-bug'>
|
||||
report issue
|
||||
</Nav.item>
|
||||
};
|
||||
22
client/homebrew/navbar/navbar.jsx
Normal file
22
client/homebrew/navbar/navbar.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
|
||||
const Navbar = React.createClass({
|
||||
render : function(){
|
||||
return <Nav.base>
|
||||
<Nav.section>
|
||||
<Nav.logo />
|
||||
<Nav.item href='/' className='homebrewLogo'>
|
||||
<div>The Homebrewery</div>
|
||||
</Nav.item>
|
||||
<Nav.item>{`v${Store.getVersion()}`}</Nav.item>
|
||||
</Nav.section>
|
||||
{this.props.children}
|
||||
</Nav.base>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Navbar;
|
||||
132
client/homebrew/navbar/navbar.less
Normal file
132
client/homebrew/navbar/navbar.less
Normal file
@@ -0,0 +1,132 @@
|
||||
@navbarHeight : 28px;
|
||||
.homebrew nav{
|
||||
.homebrewLogo{
|
||||
.animate(color);
|
||||
font-family : CodeBold;
|
||||
font-size : 12px;
|
||||
color : white;
|
||||
div{
|
||||
margin-top : 2px;
|
||||
margin-bottom : -2px;
|
||||
}
|
||||
&:hover{
|
||||
color : @blue;
|
||||
}
|
||||
}
|
||||
.brewTitle.navItem{
|
||||
font-size : 12px;
|
||||
font-weight : 800;
|
||||
color : white;
|
||||
text-align : center;
|
||||
text-transform : initial;
|
||||
}
|
||||
.patreon.navItem{
|
||||
i{
|
||||
.animate(color);
|
||||
&:hover{
|
||||
color : @red;
|
||||
}
|
||||
}
|
||||
}
|
||||
.recent.navItem{
|
||||
position : relative;
|
||||
.dropdown{
|
||||
position : absolute;
|
||||
top : 28px;
|
||||
left : 0px;
|
||||
z-index : 10000;
|
||||
width : 100%;
|
||||
h4{
|
||||
display : block;
|
||||
box-sizing : border-box;
|
||||
padding : 5px 0px;
|
||||
background-color : #333;
|
||||
font-size : 0.8em;
|
||||
color : #bbb;
|
||||
text-align : center;
|
||||
border-top : 1px solid #888;
|
||||
&:nth-of-type(1){ background-color: darken(@teal, 20%); }
|
||||
&:nth-of-type(2){ background-color: darken(@purple, 30%); }
|
||||
}
|
||||
.item{
|
||||
.animate(background-color);
|
||||
position : relative;
|
||||
display : block;
|
||||
box-sizing : border-box;
|
||||
padding : 13px 5px;
|
||||
background-color : #333;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
border-top : 1px solid #888;
|
||||
&:hover{
|
||||
background-color : @blue;
|
||||
}
|
||||
.title{
|
||||
display : inline-block;
|
||||
overflow : hidden;
|
||||
width : 100%;
|
||||
text-overflow : ellipsis;
|
||||
white-space : nowrap;
|
||||
}
|
||||
.time{
|
||||
position : absolute;
|
||||
right : 2px;
|
||||
bottom : 2px;
|
||||
font-size : 0.7em;
|
||||
color : #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.warning.navItem{
|
||||
position : relative;
|
||||
background-color : @orange;
|
||||
color : white;
|
||||
&:hover>.dropdown{
|
||||
visibility : visible;
|
||||
}
|
||||
.dropdown{
|
||||
position : absolute;
|
||||
display : block;
|
||||
top : 28px;
|
||||
left : 0px;
|
||||
visibility : hidden;
|
||||
z-index : 10000;
|
||||
box-sizing : border-box;
|
||||
width : 100%;
|
||||
padding : 13px 5px;
|
||||
background-color : #333;
|
||||
text-align : center;
|
||||
}
|
||||
}
|
||||
.staticSave.navItem{
|
||||
background-color : @orange;
|
||||
&:hover{
|
||||
background-color : @green;
|
||||
}
|
||||
}
|
||||
.continousSave.navItem{
|
||||
width : 105px;
|
||||
text-align : center;
|
||||
&.saved{
|
||||
cursor : initial;
|
||||
color : #666;
|
||||
}
|
||||
&.error{
|
||||
position : relative;
|
||||
background-color : @red;
|
||||
.errorContainer{
|
||||
position : absolute;
|
||||
top : 29px;
|
||||
left : -20px;
|
||||
z-index : 1000;
|
||||
width : 170px;
|
||||
padding : 8px;
|
||||
background-color : #333;
|
||||
a{
|
||||
color : @teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
client/homebrew/navbar/navitems.js
Normal file
10
client/homebrew/navbar/navitems.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
Account : require('./account.navitem.jsx'),
|
||||
BrewTitle : require('./brewTitle.navitem.jsx'),
|
||||
ContinousSave : require('./continousSave.navitem.jsx'),
|
||||
Issue : require('./issue.navitem.jsx'),
|
||||
Patreon : require('./patreon.navitem.jsx'),
|
||||
Print : require('./print.navitem.jsx'),
|
||||
Recent : require('./recent.navitem.jsx'),
|
||||
StaticSave : require('./staticSave.navitem.jsx'),
|
||||
};
|
||||
14
client/homebrew/navbar/patreon.navitem.jsx
Normal file
14
client/homebrew/navbar/patreon.navitem.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
var React = require('react');
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item
|
||||
{...props}
|
||||
className='patreon'
|
||||
newTab={true}
|
||||
href='https://www.patreon.com/stolksdorf'
|
||||
color='green'
|
||||
icon='fa-heart'>
|
||||
help out
|
||||
</Nav.item>
|
||||
};
|
||||
8
client/homebrew/navbar/print.navitem.jsx
Normal file
8
client/homebrew/navbar/print.navitem.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
var React = require('react');
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
module.exports = function(props){
|
||||
return <Nav.item newTab={true} href={'/print/' + props.shareId +'?dialog=true'} color='purple' icon='fa-file-pdf-o'>
|
||||
get PDF
|
||||
</Nav.item>
|
||||
};
|
||||
204
client/homebrew/navbar/recent.navitem.jsx
Normal file
204
client/homebrew/navbar/recent.navitem.jsx
Normal file
@@ -0,0 +1,204 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
var Moment = require('moment');
|
||||
|
||||
var Nav = require('naturalcrit/nav/nav.jsx');
|
||||
|
||||
const VIEW_KEY = 'homebrewery-recently-viewed';
|
||||
const EDIT_KEY = 'homebrewery-recently-edited';
|
||||
|
||||
//DEPRICATED
|
||||
|
||||
|
||||
var BaseItem = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
storageKey : '',
|
||||
text : '',
|
||||
currentBrew:{
|
||||
title : '',
|
||||
id : '',
|
||||
url : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showDropdown: false,
|
||||
brews : []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
console.log('Recent nav item is depricated');
|
||||
|
||||
var brews = JSON.parse(localStorage.getItem(this.props.storageKey) || '[]');
|
||||
|
||||
brews = _.filter(brews, (brew)=>{
|
||||
return brew.id !== this.props.currentBrew.id;
|
||||
});
|
||||
if(this.props.currentBrew.id){
|
||||
brews.unshift({
|
||||
id : this.props.currentBrew.id,
|
||||
url : this.props.currentBrew.url,
|
||||
title : this.props.currentBrew.title,
|
||||
ts : Date.now()
|
||||
});
|
||||
}
|
||||
brews = _.slice(brews, 0, 8);
|
||||
localStorage.setItem(this.props.storageKey, JSON.stringify(brews));
|
||||
this.setState({
|
||||
brews : brews
|
||||
});
|
||||
},
|
||||
|
||||
handleDropdown : function(show){
|
||||
this.setState({
|
||||
showDropdown : show
|
||||
})
|
||||
},
|
||||
|
||||
renderDropdown : function(){
|
||||
if(!this.state.showDropdown) return null;
|
||||
|
||||
var items = _.map(this.state.brews, (brew)=>{
|
||||
return <a href={brew.url} className='item' key={brew.id} target='_blank'>
|
||||
<span className='title'>{brew.title}</span>
|
||||
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||
</a>
|
||||
});
|
||||
|
||||
return <div className='dropdown'>{items}</div>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
||||
onMouseEnter={this.handleDropdown.bind(null, true)}
|
||||
onMouseLeave={this.handleDropdown.bind(null, false)}>
|
||||
{this.props.text}
|
||||
{this.renderDropdown()}
|
||||
</Nav.item>
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
viewed : React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
shareId : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
render : function(){
|
||||
return <BaseItem text='recently viewed' storageKey={VIEW_KEY}
|
||||
currentBrew={{
|
||||
id : this.props.brew.shareId,
|
||||
title : this.props.brew.title,
|
||||
url : `/share/${this.props.brew.shareId}`
|
||||
}}
|
||||
/>
|
||||
},
|
||||
}),
|
||||
|
||||
edited : React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
editId : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
render : function(){
|
||||
return <BaseItem text='recently edited' storageKey={EDIT_KEY}
|
||||
currentBrew={{
|
||||
id : this.props.brew.editId,
|
||||
title : this.props.brew.title,
|
||||
url : `/edit/${this.props.brew.editId}`
|
||||
}}
|
||||
/>
|
||||
},
|
||||
}),
|
||||
|
||||
both : React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
errorId : null
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showDropdown: false,
|
||||
edit : [],
|
||||
view : []
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
||||
var edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
|
||||
var viewed = JSON.parse(localStorage.getItem(VIEW_KEY) || '[]');
|
||||
|
||||
if(this.props.errorId){
|
||||
edited = _.filter(edited, (edit) => {
|
||||
return edit.id !== this.props.errorId;
|
||||
});
|
||||
viewed = _.filter(viewed, (view) => {
|
||||
return view.id !== this.props.errorId;
|
||||
});
|
||||
|
||||
localStorage.setItem(EDIT_KEY, JSON.stringify(edited));
|
||||
localStorage.setItem(VIEW_KEY, JSON.stringify(viewed));
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
edit : edited,
|
||||
view : viewed
|
||||
});
|
||||
},
|
||||
|
||||
handleDropdown : function(show){
|
||||
this.setState({
|
||||
showDropdown : show
|
||||
})
|
||||
},
|
||||
|
||||
renderDropdown : function(){
|
||||
if(!this.state.showDropdown) return null;
|
||||
|
||||
var makeItems = (brews) => {
|
||||
return _.map(brews, (brew)=>{
|
||||
return <a href={brew.url} className='item' key={brew.id} target='_blank'>
|
||||
<span className='title'>{brew.title}</span>
|
||||
<span className='time'>{Moment(brew.ts).fromNow()}</span>
|
||||
</a>
|
||||
});
|
||||
};
|
||||
|
||||
return <div className='dropdown'>
|
||||
<h4>edited</h4>
|
||||
{makeItems(this.state.edit)}
|
||||
<h4>viewed</h4>
|
||||
{makeItems(this.state.view)}
|
||||
</div>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <Nav.item icon='fa-clock-o' color='grey' className='recent'
|
||||
onMouseEnter={this.handleDropdown.bind(null, true)}
|
||||
onMouseLeave={this.handleDropdown.bind(null, false)}>
|
||||
Recent brews
|
||||
{this.renderDropdown()}
|
||||
</Nav.item>
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
37
client/homebrew/navbar/staticSave.navitem.jsx
Normal file
37
client/homebrew/navbar/staticSave.navitem.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
const Actions = require('homebrewery/brew.actions.js');
|
||||
|
||||
const StaticSave = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
status : 'ready'
|
||||
};
|
||||
},
|
||||
handleClick : function(){
|
||||
Actions.create();
|
||||
},
|
||||
render : function(){
|
||||
if(this.props.status === 'saving'){
|
||||
return <Nav.item icon='fa-spinner fa-spin' className='staticSave'>
|
||||
save...
|
||||
</Nav.item>
|
||||
}
|
||||
if(this.props.status === 'ready'){
|
||||
return <Nav.item icon='fa-save' className='staticSave' onClick={this.handleClick}>
|
||||
save
|
||||
</Nav.item>
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = Store.createSmartComponent(StaticSave, ()=>{
|
||||
return {
|
||||
status : Store.getStatus()
|
||||
}
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var Markdown = require('marked');
|
||||
|
||||
var PageContainer = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
text : ""
|
||||
};
|
||||
},
|
||||
|
||||
renderPages : function(){
|
||||
return _.map(this.props.text.split('\\page'), (pageText, index) => {
|
||||
return <div className='phb' dangerouslySetInnerHTML={{__html:Markdown(pageText)}} key={index} />
|
||||
})
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return <div className="pageContainer">
|
||||
{this.renderPages()}
|
||||
</div>;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = PageContainer;
|
||||
@@ -1,12 +0,0 @@
|
||||
@import (less) './client/homebrew/phbStyle/phb.style.less';
|
||||
|
||||
.pageContainer{
|
||||
padding : 30px 0px;
|
||||
background-color : @steel;
|
||||
&>.phb{
|
||||
margin-right : auto;
|
||||
margin-bottom : 30px;
|
||||
margin-left : auto;
|
||||
box-shadow : 1px 4px 14px #000;
|
||||
}
|
||||
}
|
||||
63
client/homebrew/pages/editPage/editPage.jsx
Normal file
63
client/homebrew/pages/editPage/editPage.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const Items = require('../../navbar/navitems.js');
|
||||
|
||||
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
||||
const Utils = require('homebrewery/utils.js');
|
||||
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
const Actions = require('homebrewery/brew.actions.js');
|
||||
|
||||
|
||||
const EditPage = React.createClass({
|
||||
componentDidMount: function(){
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
handleControlKeys : Utils.controlKeys({
|
||||
s : Actions.save,
|
||||
p : Actions.print
|
||||
}),
|
||||
render : function(){
|
||||
return <div className='editPage page'>
|
||||
<SmartNav />
|
||||
<div className='content'>
|
||||
<BrewInterface />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
const SmartNav = Store.createSmartComponent(React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brew : {}
|
||||
};
|
||||
},
|
||||
render : function(){
|
||||
return <Navbar>
|
||||
<Nav.section>
|
||||
<Items.BrewTitle />
|
||||
</Nav.section>
|
||||
<Nav.section>
|
||||
<Items.ContinousSave />
|
||||
<Items.Issue />
|
||||
<Nav.item newTab={true} href={'/share/' + this.props.brew.shareId} color='teal' icon='fa-share-alt'>
|
||||
Share
|
||||
</Nav.item>
|
||||
<Items.Print shareId={this.props.brew.shareId} />
|
||||
<Items.Account />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
}
|
||||
}), ()=>{
|
||||
return {brew : Store.getBrew()}
|
||||
});
|
||||
|
||||
module.exports = EditPage;
|
||||
@@ -1,5 +1,4 @@
|
||||
.editPage{
|
||||
|
||||
|
||||
|
||||
|
||||
.editPage{
|
||||
|
||||
}
|
||||
46
client/homebrew/pages/errorPage/errorPage.jsx
Normal file
46
client/homebrew/pages/errorPage/errorPage.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
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 PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
|
||||
var IssueNavItem = require('../../navbar/issue.navitem.jsx');
|
||||
var RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||
|
||||
var BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
||||
|
||||
var ErrorPage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
ver : '0.0.0',
|
||||
errorId: ''
|
||||
};
|
||||
},
|
||||
|
||||
text : '# Oops \n We could not find a brew with that id. **Sorry!**',
|
||||
|
||||
render : function(){
|
||||
return <div className='errorPage page'>
|
||||
<Navbar ver={this.props.ver}>
|
||||
<Nav.section>
|
||||
<Nav.item className='errorTitle'>
|
||||
Crit Fail!
|
||||
</Nav.item>
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
<PatreonNavItem />
|
||||
<IssueNavItem />
|
||||
<RecentNavItem.both errorId={this.props.errorId} />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
|
||||
<div className='content'>
|
||||
<BrewRenderer text={this.text} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ErrorPage;
|
||||
5
client/homebrew/pages/errorPage/errorPage.less
Normal file
5
client/homebrew/pages/errorPage/errorPage.less
Normal file
@@ -0,0 +1,5 @@
|
||||
.errorPage{
|
||||
.errorTitle{
|
||||
background-color: @orange;
|
||||
}
|
||||
}
|
||||
BIN
client/homebrew/pages/homePage/dmg_bg.jpg
Normal file
BIN
client/homebrew/pages/homePage/dmg_bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 187 KiB |
BIN
client/homebrew/pages/homePage/fantasy_background.jpg
Normal file
BIN
client/homebrew/pages/homePage/fantasy_background.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 822 KiB |
97
client/homebrew/pages/homePage/homePage.jsx
Normal file
97
client/homebrew/pages/homePage/homePage.jsx
Normal file
@@ -0,0 +1,97 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const NavItem = require('../../navbar/navitems.js');
|
||||
|
||||
|
||||
//const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
||||
const BrewCard = require('homebrewery/brewCard/brewCard.jsx');
|
||||
|
||||
//const Actions = require('homebrewery/brew.actions.js');
|
||||
|
||||
const HomePage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
topBrews : []
|
||||
};
|
||||
},
|
||||
|
||||
renderNavbar : function(){
|
||||
return <Navbar>
|
||||
<Nav.section>
|
||||
<NavItem.Patreon collaspe={true} />
|
||||
<NavItem.Issue collaspe={true} />
|
||||
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-star' collaspe={true}>
|
||||
What's new
|
||||
</Nav.item>
|
||||
<NavItem.Recent.both />
|
||||
<NavItem.Account />
|
||||
{/*}
|
||||
<Nav.item href='/new' color='green' icon='fa-external-link'>
|
||||
New Brew
|
||||
</Nav.item>
|
||||
*/}
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
},
|
||||
|
||||
renderNavigation : function(){
|
||||
return <div className='navigation'>
|
||||
<p>
|
||||
Effortlessly create Authnetic looking D&D homebrews with just text
|
||||
</p>
|
||||
<div>
|
||||
<a className='button new' target='_blank' href='/new'>
|
||||
<i className='fa fa-magic' />
|
||||
<h3>New</h3>
|
||||
<p>This is some sample text</p>
|
||||
</a>
|
||||
|
||||
<a className='button search' target='_blank' href='/search'>
|
||||
<i className='fa fa-search' />
|
||||
<h3>Search</h3>
|
||||
<p>This is some sample text</p>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a className='button docs' target='_blank' href='/docs'>
|
||||
<i className='fa fa-book' />
|
||||
<h3>Docs</h3>
|
||||
<p>This is some sample text</p>
|
||||
</a>
|
||||
<a className='button account' target='_blank' href='/account'>
|
||||
<i className='fa fa-user' />
|
||||
<h3>Account</h3>
|
||||
<p>This is some sample text</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
},
|
||||
|
||||
renderTopBrews : function(){
|
||||
return <div className='topBrews'>
|
||||
{_.map(this.props.brews, (brew)=><BrewCard brew={brew} key={brew._id}/>)}
|
||||
</div>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='homePage page'>
|
||||
{this.renderNavbar()}
|
||||
<div className='content'>
|
||||
<div className='hero'>
|
||||
hero
|
||||
<h1>The Homebrewery</h1>
|
||||
|
||||
</div>
|
||||
{this.renderNavigation()}
|
||||
{this.renderTopBrews()}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = HomePage;
|
||||
109
client/homebrew/pages/homePage/homePage.less
Normal file
109
client/homebrew/pages/homePage/homePage.less
Normal file
@@ -0,0 +1,109 @@
|
||||
|
||||
@import 'homebrewery/phb_style/phb.fonts.less';
|
||||
.homePage{
|
||||
position : relative;
|
||||
.hero{
|
||||
// .backgroundScrollAnimation();
|
||||
height : 400px;
|
||||
background-image : url('/assets/homebrew/pages/homePage/fantasy_background.jpg');
|
||||
background-position : 1% 15%;
|
||||
h1{
|
||||
margin-top : 15%;
|
||||
font-family : BookInsanity;
|
||||
font-size : 3em;
|
||||
font-weight : 800;
|
||||
letter-spacing : 0.3em;
|
||||
text-align : center;
|
||||
}
|
||||
}
|
||||
.navigation{
|
||||
padding : 30px;
|
||||
background-image : url('/assets/homebrew/pages/homePage/dmg_bg.jpg');
|
||||
text-align : center;
|
||||
&>div{
|
||||
//display : flex;
|
||||
margin : 0 auto;
|
||||
// justify-content : center;
|
||||
}
|
||||
p{
|
||||
margin-bottom : 20px;
|
||||
font-size : 0.8em;
|
||||
}
|
||||
a.button{
|
||||
.animate(background-color);
|
||||
position : relative;
|
||||
display : block;
|
||||
height : 60px;
|
||||
max-width : 280px;
|
||||
width : 100%;
|
||||
margin : 15px;
|
||||
padding : 20px 30px;
|
||||
padding-left : 80px;
|
||||
cursor : pointer;
|
||||
background-color : fade(@red, 30%);
|
||||
color : black;
|
||||
text-align : left;
|
||||
text-decoration : none;
|
||||
//flex-grow : 0;
|
||||
display: inline-block;
|
||||
i{
|
||||
position : absolute;
|
||||
top : 15px;
|
||||
left : 25px;
|
||||
font-size : 2em;
|
||||
//transform-style: preserve-3d;
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
h3{
|
||||
display : block;
|
||||
margin-bottom : 3px;
|
||||
font-size : 1.2em;
|
||||
font-weight : 600;
|
||||
letter-spacing : 0.2em;
|
||||
}
|
||||
p{
|
||||
.fadeOutLeft();
|
||||
.keep();
|
||||
opacity : 0;
|
||||
font-size : 0.8em;
|
||||
}
|
||||
&:hover{
|
||||
background-color : fade(@red, 50%);
|
||||
p{
|
||||
.fadeInRight();
|
||||
}
|
||||
i{
|
||||
transform: rotateY(360deg);
|
||||
.animateAll(0.5s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.topBrews{
|
||||
background-image : url('/assets/homebrew/pages/homePage/phb_bg.jpg');
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// .vendor(@property, @value) {
|
||||
// -webkit-@{property}: @value;
|
||||
// -khtml-@{property}: @value;
|
||||
// -moz-@{property}: @value;
|
||||
// @{property}: @value;
|
||||
// }
|
||||
// .backgroundScrollAnimation(){
|
||||
// @top : -100px;
|
||||
// .vendor(animation-iteration-count, infinite);
|
||||
// .createAnimation(backgroundScroll, 60s, linear);
|
||||
// .backgroundScrollKeyFrames(){
|
||||
// 0% { background-position: 0 @top}
|
||||
// 100% { background-position: -1200px @top}
|
||||
// }
|
||||
// @-webkit-keyframes backgroundScroll {.backgroundScrollKeyFrames();}
|
||||
// @-moz-keyframes backgroundScroll {.backgroundScrollKeyFrames();}
|
||||
// @-ms-keyframes backgroundScroll {.backgroundScrollKeyFrames();}
|
||||
// @-o-keyframes backgroundScroll {.backgroundScrollKeyFrames();}
|
||||
// @keyframes backgroundScroll {.backgroundScrollKeyFrames();}
|
||||
// }
|
||||
BIN
client/homebrew/pages/homePage/phb_bg.jpg
Normal file
BIN
client/homebrew/pages/homePage/phb_bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
62
client/homebrew/pages/newPage/newPage.jsx
Normal file
62
client/homebrew/pages/newPage/newPage.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const Items = require('../../navbar/navitems.js');
|
||||
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
const Actions = require('homebrewery/brew.actions.js');
|
||||
|
||||
const BrewInterface = require('homebrewery/brewInterface/brewInterface.jsx');
|
||||
const Utils = require('homebrewery/utils.js');
|
||||
|
||||
const KEY = 'homebrewery-new';
|
||||
|
||||
const NewPage = React.createClass({
|
||||
componentDidMount: function() {
|
||||
try{
|
||||
const storedBrew = JSON.parse(localStorage.getItem(KEY));
|
||||
if(storedBrew && storedBrew.text) Actions.setBrew(storedBrew);
|
||||
}catch(e){}
|
||||
Store.updateEmitter.on('change', this.saveToLocal);
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
Store.updateEmitter.removeListener('change', this.saveToLocal);
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
|
||||
saveToLocal : function(){
|
||||
localStorage.setItem(KEY, JSON.stringify(Store.getBrew()));
|
||||
},
|
||||
handleControlKeys : Utils.controlKeys({
|
||||
s : Actions.saveNew,
|
||||
p : Actions.localPrint
|
||||
}),
|
||||
|
||||
render : function(){
|
||||
return <div className='newPage page'>
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<Items.BrewTitle />
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
<Items.StaticSave />
|
||||
<Nav.item color='purple' icon='fa-file-pdf-o' onClick={Actions.localPrint}>
|
||||
get PDF
|
||||
</Nav.item>
|
||||
<Items.Issue collaspe={true} />
|
||||
<Items.Account />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
|
||||
<div className='content'>
|
||||
<BrewInterface />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NewPage;
|
||||
4
client/homebrew/pages/newPage/newPage.less
Normal file
4
client/homebrew/pages/newPage/newPage.less
Normal file
@@ -0,0 +1,4 @@
|
||||
.newPage{
|
||||
|
||||
|
||||
}
|
||||
72
client/homebrew/pages/printPage/printPage.jsx
Normal file
72
client/homebrew/pages/printPage/printPage.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
|
||||
const BrewRenderer = require('homebrewery/brewRenderer/brewRenderer.jsx');
|
||||
|
||||
const Markdown = require('homebrewery/markdown.js');
|
||||
|
||||
const Headtags = require('vitreum/headtags');
|
||||
|
||||
const PrintPage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
query : {},
|
||||
brew : {
|
||||
text : '',
|
||||
style : ''
|
||||
}
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
brew: this.props.brew
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
if(this.props.query.local){
|
||||
try{
|
||||
this.setState({
|
||||
brew : JSON.parse(
|
||||
localStorage.getItem(this.props.query.local)
|
||||
)
|
||||
});
|
||||
}catch(e){}
|
||||
}
|
||||
//if(this.props.query.dialog) window.print();
|
||||
},
|
||||
//TODO: Print page shouldn't replicate functionality in brew renderer
|
||||
renderStyle : function(){
|
||||
if(!this.state.brew.style) return;
|
||||
return <style>{this.state.brew.style.replace(/;/g, ' !important;')}</style>
|
||||
},
|
||||
renderPages : function(){
|
||||
return _.map(this.state.brew.text.split('\\page'), (page, index) => {
|
||||
return <div
|
||||
className='phb v2'
|
||||
id={`p${index + 1}`}
|
||||
dangerouslySetInnerHTML={{__html:Markdown.render(page)}}
|
||||
key={index} />;
|
||||
});
|
||||
},
|
||||
|
||||
renderPrintInstructions : function(){
|
||||
return <div className='printInstructions'>
|
||||
Hey, I'm really cool instructions!!!!!
|
||||
|
||||
</div>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='printPage'>
|
||||
<Headtags.title>{this.state.brew.title}</Headtags.title>
|
||||
{this.renderPrintInstructions()}
|
||||
|
||||
<BrewRenderer text={this.state.brew.text} style={this.state.brew.style} />
|
||||
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = PrintPage;
|
||||
17
client/homebrew/pages/printPage/printPage.less
Normal file
17
client/homebrew/pages/printPage/printPage.less
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
.printPage{
|
||||
position : relative;
|
||||
@media print{
|
||||
.printInstructions{
|
||||
display : none;
|
||||
}
|
||||
}
|
||||
.printInstructions{
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
z-index : 100000;
|
||||
padding : 30px;
|
||||
background-color : @blue;
|
||||
}
|
||||
}
|
||||
74
client/homebrew/pages/sharePage/sharePage.jsx
Normal file
74
client/homebrew/pages/sharePage/sharePage.jsx
Normal file
@@ -0,0 +1,74 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
const PrintLink = require('../../navbar/print.navitem.jsx');
|
||||
const ReportIssue = require('../../navbar/issue.navitem.jsx');
|
||||
//const RecentlyViewed = require('../../navbar/recent.navitem.jsx').viewed;
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
|
||||
const BrewView = require('homebrewery/BrewView/BrewView.jsx');
|
||||
const Utils = require('homebrewery/utils.js');
|
||||
|
||||
const Actions = require('homebrewery/brew.actions.js');
|
||||
const Store = require('homebrewery/brew.store.js');
|
||||
|
||||
const Headtags = require('vitreum/headtags');
|
||||
|
||||
const SharePage = React.createClass({
|
||||
componentDidMount: function() {
|
||||
document.addEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
document.removeEventListener('keydown', this.handleControlKeys);
|
||||
},
|
||||
handleControlKeys : Utils.controlKeys({
|
||||
p : Actions.print
|
||||
}),
|
||||
|
||||
renderMetatags : function(brew){
|
||||
let metatags = [
|
||||
<Headtags.meta key='site_name' property='og:site_name' content='Homebrewery'/>,
|
||||
<Headtags.meta key='type' property='og:type' content='article' />
|
||||
];
|
||||
if(brew.title){
|
||||
metatags.push(<Headtags.meta key='title' property='og:title' content={brew.title} />);
|
||||
}
|
||||
if(brew.description){
|
||||
metatags.push(<Headtags.meta key='description' name='description' content={brew.description} />);
|
||||
}
|
||||
if(brew.thumbnail){
|
||||
metatags.push(<Headtags.meta key='image' property='og:image' content={brew.thumbnail} />);
|
||||
}
|
||||
return metatags;
|
||||
},
|
||||
|
||||
render : function(){
|
||||
const brew = Store.getBrew();
|
||||
return <div className='sharePage page'>
|
||||
{this.renderMetatags(brew)}
|
||||
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<Nav.item className='brewTitle'>{brew.title}</Nav.item>
|
||||
</Nav.section>
|
||||
|
||||
<Nav.section>
|
||||
<ReportIssue collaspe={true} />
|
||||
<PrintLink shareId={brew.shareId} />
|
||||
<Nav.item href={'/source/' + brew.shareId} color='teal' icon='fa-code'>
|
||||
source
|
||||
</Nav.item>
|
||||
<Account />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
<div className='content'>
|
||||
<BrewView brew={brew}/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = SharePage;
|
||||
@@ -1,3 +1,3 @@
|
||||
.sharePage{
|
||||
|
||||
.sharePage{
|
||||
|
||||
}
|
||||
58
client/homebrew/pages/userPage/brewItem/brewItem.jsx
Normal file
58
client/homebrew/pages/userPage/brewItem/brewItem.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
const moment = require('moment');
|
||||
|
||||
//TODO: Depriacte
|
||||
|
||||
const BrewItem = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
brew : {
|
||||
title : '',
|
||||
description : '',
|
||||
|
||||
authors : []
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
renderEditLink: function(){
|
||||
if(!this.props.brew.editId) return;
|
||||
|
||||
return <a href={`/edit/${this.props.brew.editId}`} target='_blank'>
|
||||
<i className='fa fa-pencil' />
|
||||
</a>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
console.log('Brew Item should be depricated');
|
||||
const brew = this.props.brew;
|
||||
return <div className='brewItem'>
|
||||
<h2>{brew.title}</h2>
|
||||
<p className='description' >{brew.description}</p>
|
||||
<hr />
|
||||
|
||||
<div className='info'>
|
||||
<span>
|
||||
<i className='fa fa-user' /> {brew.authors.join(', ')}
|
||||
</span>
|
||||
<span>
|
||||
<i className='fa fa-eye' /> {brew.views}
|
||||
</span>
|
||||
<span>
|
||||
<i className='fa fa-refresh' /> {moment(brew.updatedAt).fromNow()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='links'>
|
||||
<a href={`/share/${brew.shareId}`} target='_blank'>
|
||||
<i className='fa fa-share-alt' />
|
||||
</a>
|
||||
{this.renderEditLink()}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = BrewItem;
|
||||
60
client/homebrew/pages/userPage/brewItem/brewItem.less
Normal file
60
client/homebrew/pages/userPage/brewItem/brewItem.less
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
.brewItem{
|
||||
position : relative;
|
||||
display : inline-block;
|
||||
vertical-align : top;
|
||||
box-sizing : border-box;
|
||||
box-sizing : border-box;
|
||||
overflow : hidden;
|
||||
width : 48%;
|
||||
margin-right : 15px;
|
||||
margin-bottom : 15px;
|
||||
padding : 5px 15px 5px 8px;
|
||||
padding-right : 15px;
|
||||
border : 1px solid #c9ad6a;
|
||||
border-radius : 5px;
|
||||
-webkit-column-break-inside : avoid;
|
||||
page-break-inside : avoid;
|
||||
break-inside : avoid;
|
||||
h4{
|
||||
margin-bottom : 5px;
|
||||
font-size : 2.2em;
|
||||
}
|
||||
.info{
|
||||
font-family : ScalySans;
|
||||
font-size : 1.2em;
|
||||
&>span{
|
||||
margin-right : 15px;
|
||||
}
|
||||
}
|
||||
&:hover{
|
||||
.links{
|
||||
opacity : 1;
|
||||
}
|
||||
}
|
||||
&:nth-child(2n + 1){
|
||||
margin-right : 0px;
|
||||
}
|
||||
.links{
|
||||
.animate(opacity);
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
height : 100%;
|
||||
width : 2em;
|
||||
opacity : 0;
|
||||
background-color : fade(black, 60%);
|
||||
text-align : center;
|
||||
a{
|
||||
.animate(opacity);
|
||||
display : block;
|
||||
margin : 8px 0px;
|
||||
opacity : 0.6;
|
||||
font-size : 1.3em;
|
||||
color : white;
|
||||
&:hover{
|
||||
opacity : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
client/homebrew/pages/userPage/userPage.jsx
Normal file
73
client/homebrew/pages/userPage/userPage.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
const React = require('react');
|
||||
const _ = require('lodash');
|
||||
const cx = require('classnames');
|
||||
|
||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const Navbar = require('../../navbar/navbar.jsx');
|
||||
|
||||
const RecentNavItem = require('../../navbar/recent.navitem.jsx');
|
||||
const Account = require('../../navbar/account.navitem.jsx');
|
||||
const BrewItem = require('./brewItem/brewItem.jsx');
|
||||
|
||||
// const brew = {
|
||||
// title : 'SUPER Long title woah now',
|
||||
// authors : []
|
||||
// }
|
||||
|
||||
// const BREWS = _.times(25, ()=>{ return brew});
|
||||
|
||||
|
||||
const UserPage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
username : '',
|
||||
brews : []
|
||||
};
|
||||
},
|
||||
|
||||
renderBrews : function(brews){
|
||||
if(!brews || !brews.length) return <div className='noBrews'>No Brews.</div>;
|
||||
|
||||
const sortedBrews = _.sortBy(brews, (brew)=>{ return brew.title; });
|
||||
return _.map(sortedBrews, (brew, idx) => {
|
||||
return <BrewItem brew={brew} key={idx}/>
|
||||
});
|
||||
},
|
||||
|
||||
getSortedBrews : function(){
|
||||
return _.groupBy(this.props.brews, (brew)=>{
|
||||
return (brew.published ? 'published' : 'private')
|
||||
});
|
||||
},
|
||||
|
||||
renderPrivateBrews : function(privateBrews){
|
||||
if(!privateBrews || !privateBrews.length) return;
|
||||
|
||||
return [
|
||||
<h1>{this.props.username}'s unpublished brews</h1>,
|
||||
this.renderBrews(privateBrews)
|
||||
];
|
||||
},
|
||||
|
||||
render : function(){
|
||||
const brews = this.getSortedBrews();
|
||||
return <div className='userPage page'>
|
||||
<Navbar>
|
||||
<Nav.section>
|
||||
<RecentNavItem.both />
|
||||
<Account userPage={this.props.username} />
|
||||
</Nav.section>
|
||||
</Navbar>
|
||||
|
||||
<div className='content'>
|
||||
<div className='phb'>
|
||||
<h1>{this.props.username}'s brews</h1>
|
||||
{this.renderBrews(brews.published)}
|
||||
{this.renderPrivateBrews(brews.private)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = UserPage;
|
||||
33
client/homebrew/pages/userPage/userPage.less
Normal file
33
client/homebrew/pages/userPage/userPage.less
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
.noColumns(){
|
||||
column-count : auto;
|
||||
column-fill : auto;
|
||||
column-gap : auto;
|
||||
column-width : auto;
|
||||
-webkit-column-count : auto;
|
||||
-moz-column-count : auto;
|
||||
-webkit-column-width : auto;
|
||||
-moz-column-width : auto;
|
||||
-webkit-column-gap : auto;
|
||||
-moz-column-gap : auto;
|
||||
}
|
||||
.userPage{
|
||||
.content{
|
||||
overflow-y : scroll;
|
||||
.phb{
|
||||
.noColumns();
|
||||
height : auto;
|
||||
min-height : 279.4mm;
|
||||
margin : 20px auto;
|
||||
&::after{
|
||||
display : none;
|
||||
}
|
||||
.noBrews{
|
||||
margin : 10px 0px;
|
||||
font-size : 1.3em;
|
||||
font-style : italic;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
|
||||
@import (less) 'shared/naturalCrit/styles/reset.less';
|
||||
@import (less) './client/homebrew/phbStyle/phb.fonts.css';
|
||||
@import (less) './client/homebrew/phbStyle/phb.assets.less';
|
||||
//Colors
|
||||
@background : #EEE5CE;
|
||||
@noteGreen : #e0e5c1;
|
||||
@headerUnderline : #c9ad6a;
|
||||
@horizontalRule : #9c2b1b;
|
||||
@headerText : #58180D;
|
||||
@monsterStatBackground : #FDF1DC;
|
||||
.useSansSerif(){
|
||||
font-family : ScalySans;
|
||||
em{
|
||||
font-family : ScalySans;
|
||||
font-style : italic;
|
||||
}
|
||||
strong{
|
||||
font-family : ScalySans;
|
||||
font-weight : 800;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
}
|
||||
.useColumns(){
|
||||
column-count : 2;
|
||||
column-fill : auto;
|
||||
column-gap : 1cm;
|
||||
column-width : 8cm;
|
||||
-webkit-column-count : 2;
|
||||
-moz-column-count : 2;
|
||||
-webkit-column-width : 8cm;
|
||||
-moz-column-width : 8cm;
|
||||
-webkit-column-gap : 1cm;
|
||||
-moz-column-gap : 1cm;
|
||||
}
|
||||
.phb{
|
||||
.useColumns();
|
||||
position : relative;
|
||||
z-index : 15;
|
||||
box-sizing : border-box;
|
||||
overflow : hidden;
|
||||
height : 279.4mm;
|
||||
width : 215.9mm;
|
||||
padding : 1.0cm 1.7cm;
|
||||
padding-bottom : 1.5cm;
|
||||
background-color : @background;
|
||||
background-image : @backgroundImage;
|
||||
font-family : BookSanity;
|
||||
font-size : 9pt;
|
||||
text-rendering : optimizeLegibility;
|
||||
//*****************************
|
||||
// * BASE
|
||||
// *****************************/
|
||||
p{
|
||||
padding-bottom : 0.8em;
|
||||
line-height : 1.3em;
|
||||
&+p{
|
||||
margin-top : -0.8em;
|
||||
text-indent : 1em;
|
||||
}
|
||||
}
|
||||
ul{
|
||||
margin-bottom : 0.8em;
|
||||
line-height : 1.3em;
|
||||
list-style-position : inside;
|
||||
list-style-type : disc;
|
||||
}
|
||||
ol{
|
||||
list-style-position: inside;
|
||||
margin-bottom : 0.8em;
|
||||
line-height : 1.3em;
|
||||
list-style-type : decimal;
|
||||
}
|
||||
img{
|
||||
z-index : -1;
|
||||
}
|
||||
strong{
|
||||
font-weight : bold;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
em{
|
||||
font-style : italic;
|
||||
}
|
||||
//*****************************
|
||||
// * HEADERS
|
||||
// *****************************/
|
||||
h1,h2,h3,h4{
|
||||
margin-top : 0.2em;
|
||||
margin-bottom : 0.2em;
|
||||
font-family : MrJeeves;
|
||||
font-weight : 800;
|
||||
color : @headerText;
|
||||
}
|
||||
h1{
|
||||
column-span : 2;
|
||||
font-size : 28pt;
|
||||
-webkit-column-span : 2;
|
||||
-moz-column-span : 2;
|
||||
&+p::first-letter{
|
||||
float : left;
|
||||
font-family : Solberry;
|
||||
line-height: 0.8em;
|
||||
font-size : 10em;
|
||||
color : #222;
|
||||
}
|
||||
}
|
||||
h2{
|
||||
font-size : 20pt;
|
||||
}
|
||||
h3{
|
||||
font-size : 15pt;
|
||||
border-bottom : 2px solid @headerUnderline;
|
||||
}
|
||||
h4{
|
||||
margin-bottom : 0.00em;
|
||||
font-size : 12pt;
|
||||
}
|
||||
h5{
|
||||
margin-bottom : 0.2em;
|
||||
font-family : ScalySansSmallCaps;
|
||||
font-size : 13pt;
|
||||
font-weight : 900;
|
||||
}
|
||||
//*****************************
|
||||
// * TABLE
|
||||
// *****************************/
|
||||
table{
|
||||
.useSansSerif();
|
||||
width : 100%;
|
||||
margin-bottom : 1em;
|
||||
font-size : 10pt;
|
||||
thead{
|
||||
font-weight : 800;
|
||||
th{
|
||||
vertical-align : bottom;
|
||||
padding-bottom : 0.3em;
|
||||
padding-right : 0.1em;
|
||||
padding-left : 0.1em;
|
||||
}
|
||||
}
|
||||
tbody{
|
||||
tr{
|
||||
td{
|
||||
padding : 0.3em 0.1em;
|
||||
}
|
||||
&:nth-child(odd){
|
||||
background-color : @noteGreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
// * NOTE
|
||||
// *****************************/
|
||||
blockquote{
|
||||
.useSansSerif();
|
||||
box-sizing : border-box;
|
||||
margin-bottom : 1em;
|
||||
padding : 5px 10px;
|
||||
background-color : @noteGreen;
|
||||
border-top : 2px black solid;
|
||||
border-bottom : 2px black solid;
|
||||
box-shadow : 1px 4px 14px #888;
|
||||
p, ul{
|
||||
font-size : 10pt;
|
||||
line-height : 1.1em;
|
||||
}
|
||||
}
|
||||
//*****************************
|
||||
// * MONSTER STAT BLOCK
|
||||
// *****************************/
|
||||
hr+blockquote{
|
||||
position : relative;
|
||||
background-color : @monsterStatBackground;
|
||||
border : none;
|
||||
h2{
|
||||
margin-bottom : 0px;
|
||||
&+p{
|
||||
padding-bottom : 0px;
|
||||
}
|
||||
}
|
||||
h3{
|
||||
font-family : ScalySans;
|
||||
font-weight : 400;
|
||||
border-bottom : 1px solid @headerText;
|
||||
}
|
||||
ul{
|
||||
.useSansSerif();
|
||||
padding-left : 1em;
|
||||
font-size : 10pt;
|
||||
color : @headerText;
|
||||
text-indent : -1em;
|
||||
list-style-type : none;
|
||||
}
|
||||
// Monster Ability table
|
||||
hr+table{
|
||||
margin : 0;
|
||||
column-span : 1;
|
||||
background-color : transparent;
|
||||
border-image : none;
|
||||
-webkit-column-span : 1;
|
||||
tbody{
|
||||
tr:nth-child(odd), tr:nth-child(even){
|
||||
background-color : transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
table{
|
||||
color : @headerText;
|
||||
}
|
||||
p+p{
|
||||
margin-top : 0em;
|
||||
padding-bottom : 0.5em;
|
||||
text-indent : 0em;
|
||||
}
|
||||
//Triangle dividers
|
||||
hr{
|
||||
@height : 3px;
|
||||
position : relative;
|
||||
visibility : visible;
|
||||
margin : 8px 0px;
|
||||
border-color : transparent;
|
||||
&:after, &:before{
|
||||
content : "";
|
||||
position : absolute;
|
||||
left : 0px;
|
||||
height : @height;
|
||||
width : 100%;
|
||||
}
|
||||
&:before{
|
||||
top : -@height;
|
||||
background : linear-gradient(to right top, @horizontalRule 40%, transparent 50%)
|
||||
}
|
||||
&:after{
|
||||
top : 0px;
|
||||
background : linear-gradient(to right bottom, @horizontalRule 40%, transparent 50%)
|
||||
}
|
||||
}
|
||||
//Top and Bottom Borders
|
||||
&:after, &:before{
|
||||
content : "";
|
||||
position : absolute;
|
||||
height : 4px;
|
||||
width : 100%;
|
||||
padding : 0px 3px;
|
||||
background-color : #E69A28;
|
||||
border : 1px solid black;
|
||||
}
|
||||
&:before{
|
||||
top : 0px;
|
||||
left : -3px;
|
||||
}
|
||||
&:after{
|
||||
bottom : 0px;
|
||||
left : -3px;
|
||||
}
|
||||
}
|
||||
//Full Width
|
||||
hr+hr+blockquote{
|
||||
.useColumns();
|
||||
}
|
||||
//*****************************
|
||||
// * FULL CLASS TABLE
|
||||
// *****************************/
|
||||
hr+table{
|
||||
margin-top : -5px;
|
||||
margin-bottom : 50px;
|
||||
padding-top : 10px;
|
||||
border-collapse : separate;
|
||||
background-color : white;
|
||||
border : initial;
|
||||
border-image-outset : 37px 17px;
|
||||
border-image-repeat : round;
|
||||
border-image-slice : 150 200 150 200;
|
||||
border-image-source : @frameBorderImage;
|
||||
border-image-width : 47px;
|
||||
}
|
||||
//*****************************
|
||||
// * FOOTER
|
||||
// *****************************/
|
||||
&:after{
|
||||
content : "";
|
||||
position : absolute;
|
||||
bottom : 0px;
|
||||
left : 0px;
|
||||
height : 50px;
|
||||
width : 100%;
|
||||
background-image : @footerAccentImage;
|
||||
background-size : cover;
|
||||
}
|
||||
&:nth-child(even){
|
||||
&:after{
|
||||
transform : scaleX(-1);
|
||||
}
|
||||
.pageNumber{
|
||||
left : 2px;
|
||||
}
|
||||
.footnote{
|
||||
left : 80px;
|
||||
text-align : left;
|
||||
}
|
||||
}
|
||||
.pageNumber{
|
||||
position : absolute;
|
||||
right : 2px;
|
||||
bottom : 22px;
|
||||
width : 50px;
|
||||
font-size : 0.9em;
|
||||
color : #c9ad6a;
|
||||
text-align : center;
|
||||
}
|
||||
.footnote{
|
||||
position : absolute;
|
||||
right : 80px;
|
||||
bottom : 32px;
|
||||
width : 200px;
|
||||
font-size : 0.8em;
|
||||
color : #c9ad6a;
|
||||
text-align : right;
|
||||
}
|
||||
//*****************************
|
||||
// * EXTRAS
|
||||
// *****************************/
|
||||
hr{
|
||||
visibility : hidden;
|
||||
margin : 0px;
|
||||
}
|
||||
//Modified unorder list, used in spells
|
||||
hr+ul{
|
||||
margin-bottom : 0.5em;
|
||||
padding-left : 1em;
|
||||
text-indent : -1em;
|
||||
list-style-type : none;
|
||||
}
|
||||
//Double hr for full width elements
|
||||
hr+hr+table, hr+hr+blockquote{
|
||||
column-span : all;
|
||||
-webkit-column-span : all;
|
||||
-moz-column-span : all;
|
||||
}
|
||||
//Column Break
|
||||
pre{
|
||||
visibility : hidden;
|
||||
-webkit-column-break-after : always;
|
||||
break-after : always;
|
||||
-moz-column-break-after : always;
|
||||
}
|
||||
//Avoid breaking up
|
||||
p,ul,blockquote,table{
|
||||
z-index : 15;
|
||||
-webkit-column-break-inside : avoid;
|
||||
column-break-inside : avoid;
|
||||
overflow: hidden; /* Firefox fix */
|
||||
}
|
||||
//Better spacing for spell blocks
|
||||
h4+p+hr+ul{
|
||||
margin-top : -0.5em
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var Statusbar = require('../statusbar/statusbar.jsx');
|
||||
|
||||
var PageContainer = require('../pageContainer/pageContainer.jsx');
|
||||
|
||||
var SharePage = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
id : null,
|
||||
entry : {
|
||||
text : "",
|
||||
shareId : null,
|
||||
editId : null,
|
||||
createdAt : null,
|
||||
updatedAt : null,
|
||||
views : 0
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return(
|
||||
<div className='sharePage'>
|
||||
<Statusbar
|
||||
sourceText={this.props.entry.text}
|
||||
lastUpdated={this.props.entry.updatedAt}
|
||||
views={this.props.entry.views}
|
||||
printId={this.props.entry.shareId}
|
||||
/>
|
||||
|
||||
<PageContainer text={this.props.entry.text} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = SharePage;
|
||||
@@ -1,134 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
var Moment = require('moment');
|
||||
|
||||
var Logo = require('naturalCrit/logo/logo.jsx');
|
||||
|
||||
var replaceAll = function(str, find, replace) {
|
||||
return str.replace(new RegExp(find, 'g'), replace);
|
||||
}
|
||||
|
||||
var Statusbar = React.createClass({
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
//editId: null,
|
||||
sourceText : null,
|
||||
shareId : null,
|
||||
printId : null,
|
||||
isPending : false,
|
||||
lastUpdated : null,
|
||||
info : null,
|
||||
views : 0
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
//Updates the last updated text every 10 seconds
|
||||
if(this.props.lastUpdated){
|
||||
this.refreshTimer = setInterval(()=>{
|
||||
this.forceUpdate();
|
||||
}, 10000)
|
||||
}
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
clearInterval(this.refreshTimer);
|
||||
},
|
||||
|
||||
|
||||
openSourceWindow : function(){
|
||||
var sourceWindow = window.open();
|
||||
|
||||
var content = replaceAll(this.props.sourceText, '<', '<');
|
||||
content = replaceAll(content, '>', '>');
|
||||
|
||||
console.log(content);
|
||||
|
||||
sourceWindow.document.write('<code><pre>' + content + '</pre></code>');
|
||||
},
|
||||
|
||||
|
||||
|
||||
renderInfo : function(){
|
||||
if(!this.props.lastUpdated) return null;
|
||||
|
||||
return [
|
||||
<div className='views' key='views'>
|
||||
Views: {this.props.views}
|
||||
</div>,
|
||||
<div className='lastUpdated' key='lastUpdated'>
|
||||
Last updated: {Moment(this.props.lastUpdated).fromNow()}
|
||||
</div>
|
||||
];
|
||||
|
||||
},
|
||||
|
||||
renderSourceButton : function(){
|
||||
if(!this.props.sourceText) return null;
|
||||
|
||||
return <a className='sourceField' onClick={this.openSourceWindow}>
|
||||
View Source <i className='fa fa-code' />
|
||||
</a>
|
||||
},
|
||||
|
||||
renderNewButton : function(){
|
||||
if(this.props.editId || this.props.shareId) return null;
|
||||
|
||||
return <a className='newButton' target='_blank' href='/homebrew/new'>
|
||||
New Brew <i className='fa fa-external-link' />
|
||||
</a>
|
||||
},
|
||||
|
||||
renderShare : function(){
|
||||
if(!this.props.shareId) return null;
|
||||
|
||||
return <a className='shareField' key='share' href={'/homebrew/share/' + this.props.shareId} target="_blank">
|
||||
Share Link <i className='fa fa-external-link' />
|
||||
</a>
|
||||
},
|
||||
|
||||
renderPrintButton : function(){
|
||||
if(!this.props.printId) return null;
|
||||
|
||||
return <a className='printField' key='print' href={'/homebrew/print/' + this.props.printId} target="_blank">
|
||||
Print View <i className='fa fa-print' />
|
||||
</a>
|
||||
},
|
||||
|
||||
renderStatus : function(){
|
||||
if(!this.props.editId) return null;
|
||||
|
||||
var text = 'Saved.'
|
||||
if(this.props.isPending){
|
||||
text = 'Saving...'
|
||||
}
|
||||
return <div className='savingStatus'>
|
||||
{text}
|
||||
</div>
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='statusbar'>
|
||||
<Logo
|
||||
hoverSlide={true}
|
||||
/>
|
||||
<div className='left'>
|
||||
<a href='/homebrew' className='toolName'>
|
||||
The Home<small>Brewery</small>
|
||||
</a>
|
||||
</div>
|
||||
<div className='controls right'>
|
||||
{this.renderStatus()}
|
||||
{this.renderInfo()}
|
||||
{this.renderSourceButton()}
|
||||
{this.renderPrintButton()}
|
||||
{this.renderShare()}
|
||||
{this.renderNewButton()}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Statusbar;
|
||||
@@ -1,110 +0,0 @@
|
||||
|
||||
.statusbar{
|
||||
position : fixed;
|
||||
z-index : 1000;
|
||||
height : 25px;
|
||||
width : 100%;
|
||||
background-color : black;
|
||||
font-size : 24px;
|
||||
color : white;
|
||||
line-height : 1.0em;
|
||||
border-bottom : 1px solid @grey;
|
||||
.logo{
|
||||
display : inline-block;
|
||||
vertical-align : middle;
|
||||
margin-top : -5px;
|
||||
margin-right : 20px;
|
||||
svg{
|
||||
margin-top : -6px;
|
||||
}
|
||||
}
|
||||
.left{
|
||||
display : inline-block;
|
||||
vertical-align : top;
|
||||
}
|
||||
.right{
|
||||
float : right;
|
||||
}
|
||||
.toolName{
|
||||
display : block;
|
||||
vertical-align : middle;
|
||||
font-family : CodeBold;
|
||||
font-size : 16px;
|
||||
color : white;
|
||||
line-height : 30px;
|
||||
text-decoration : none;
|
||||
small{
|
||||
font-family : CodeBold;
|
||||
}
|
||||
}
|
||||
.controls{
|
||||
font-size : 12px;
|
||||
>*{
|
||||
display : inline-block;
|
||||
height : 100%;
|
||||
padding : 0px 10px;
|
||||
border-left : 1px solid @grey;
|
||||
}
|
||||
.savingStatus{
|
||||
width : 56px;
|
||||
color : @grey;
|
||||
text-align : center;
|
||||
}
|
||||
.newButton{
|
||||
.animate(background-color);
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
&:hover{
|
||||
background-color : fade(@green, 70%);
|
||||
}
|
||||
}
|
||||
.shareField{
|
||||
.animate(background-color);
|
||||
cursor : pointer;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
&:hover{
|
||||
background-color : fade(@teal, 70%);
|
||||
}
|
||||
span{
|
||||
margin-right : 5px;
|
||||
}
|
||||
input{
|
||||
width : 100px;
|
||||
font-size : 12px;
|
||||
}
|
||||
}
|
||||
.printField{
|
||||
.animate(background-color);
|
||||
cursor : pointer;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
&:hover{
|
||||
background-color : fade(@orange, 70%);
|
||||
}
|
||||
span{
|
||||
margin-right : 5px;
|
||||
}
|
||||
input{
|
||||
width : 100px;
|
||||
font-size : 12px;
|
||||
}
|
||||
}
|
||||
.sourceField{
|
||||
.animate(background-color);
|
||||
cursor : pointer;
|
||||
color : white;
|
||||
text-decoration : none;
|
||||
&:hover{
|
||||
background-color : fade(@teal, 70%);
|
||||
}
|
||||
span{
|
||||
margin-right : 5px;
|
||||
}
|
||||
input{
|
||||
width : 100px;
|
||||
font-size : 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var Sidebar = require('./sidebar/sidebar.jsx');
|
||||
var Encounter = require('./encounter/encounter.jsx');
|
||||
|
||||
var encounters = [
|
||||
{
|
||||
name : 'The Big Bad',
|
||||
desc : 'The big fight!',
|
||||
reward : 'gems',
|
||||
enemies : ['goblin', 'goblin'],
|
||||
reserve : ['goblin'],
|
||||
},
|
||||
{
|
||||
name : 'Demon Goats',
|
||||
desc : 'Gross fight',
|
||||
reward : 'curved horn, goat sac',
|
||||
enemies : ['demon_goat', 'demon_goat', 'demon_goat'],
|
||||
unique : {
|
||||
demon_goat : {
|
||||
"hp" : 140,
|
||||
"ac" : 16,
|
||||
"attr" : {
|
||||
"str" : 8,
|
||||
"con" : 8,
|
||||
"dex" : 8,
|
||||
"int" : 8,
|
||||
"wis" : 8,
|
||||
"cha" : 8
|
||||
},
|
||||
"attacks" : {
|
||||
"charge" : {
|
||||
"atk" : "1d20+5",
|
||||
"dmg" : "1d8+5",
|
||||
"type" : "bludge"
|
||||
}
|
||||
},
|
||||
"abilities" : ["charge"],
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
var defaultMonsterManual = require('naturalCrit/defaultMonsterManual.js');
|
||||
|
||||
var attrMod = function(attr){
|
||||
return Math.floor(attr/2) - 5;
|
||||
}
|
||||
|
||||
|
||||
var Store = require('naturalCrit/combat.store');
|
||||
var Actions = require('naturalCrit/combat.actions');
|
||||
|
||||
|
||||
|
||||
var CombatManager = React.createClass({
|
||||
mixins : [Store.mixin()],
|
||||
|
||||
|
||||
getInitialState: function() {
|
||||
var self = this;
|
||||
return {
|
||||
selectedEncounterIndex : 0,
|
||||
encounters : JSON.parse(localStorage.getItem('encounters')) || encounters,
|
||||
monsterManual : JSON.parse(localStorage.getItem('monsterManual')) || defaultMonsterManual,
|
||||
|
||||
players : localStorage.getItem('players') || 'jasper 13\nzatch 19',
|
||||
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
onStoreChange : function(){
|
||||
console.log('STORE CAHNGE', Store.getInc());
|
||||
this.setState({
|
||||
inc : Store.getInc()
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
handleEncounterJSONChange : function(encounterIndex, json){
|
||||
this.state.encounters[encounterIndex] = json;
|
||||
this.setState({
|
||||
encounters : this.state.encounters
|
||||
})
|
||||
localStorage.setItem("encounters", JSON.stringify(this.state.encounters));
|
||||
},
|
||||
handleMonsterManualJSONChange : function(json){
|
||||
this.setState({
|
||||
monsterManual : json
|
||||
});
|
||||
localStorage.setItem("monsterManual", JSON.stringify(this.state.monsterManual));
|
||||
},
|
||||
handlePlayerChange : function(e){
|
||||
this.setState({
|
||||
players : e.target.value
|
||||
});
|
||||
localStorage.setItem("players", e.target.value);
|
||||
},
|
||||
handleSelectedEncounterChange : function(encounterIndex){
|
||||
console.log(encounterIndex);
|
||||
this.setState({
|
||||
selectedEncounterIndex : encounterIndex
|
||||
});
|
||||
},
|
||||
handleRemoveEncounter : function(encounterIndex){
|
||||
this.state.encounters.splice(encounterIndex, 1);
|
||||
this.setState({
|
||||
encounters : this.state.encounters
|
||||
});
|
||||
localStorage.setItem("encounters", JSON.stringify(this.state.encounters));
|
||||
},
|
||||
|
||||
renderSelectedEncounter : function(){
|
||||
var self = this;
|
||||
|
||||
if(this.state.selectedEncounterIndex != null && this.state.encounters[this.state.selectedEncounterIndex]){
|
||||
var selectedEncounter = this.state.encounters[this.state.selectedEncounterIndex]
|
||||
return <Encounter
|
||||
key={selectedEncounter.name}
|
||||
{...selectedEncounter}
|
||||
monsterManual={this.state.monsterManual}
|
||||
players={this.state.players}
|
||||
/>
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
temp : function(){
|
||||
Actions.setInc(++this.state.inc);
|
||||
},
|
||||
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className='combatManager'>
|
||||
<Sidebar
|
||||
selectedEncounter={this.state.selectedEncounterIndex}
|
||||
encounters={this.state.encounters}
|
||||
monsterManual={this.state.monsterManual}
|
||||
players={this.state.players}
|
||||
|
||||
onSelectEncounter={this.handleSelectedEncounterChange}
|
||||
onRemoveEncounter={this.handleRemoveEncounter}
|
||||
onJSONChange={this.handleEncounterJSONChange}
|
||||
onMonsterManualChange={this.handleMonsterManualJSONChange}
|
||||
onPlayerChange={this.handlePlayerChange}
|
||||
/>
|
||||
|
||||
|
||||
{this.renderSelectedEncounter()}
|
||||
|
||||
<button onClick={this.temp}>YUP {this.state.inc}</button>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = CombatManager;
|
||||
@@ -1,8 +0,0 @@
|
||||
.combatManager{
|
||||
|
||||
.encounterContainer{
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var Store = require('naturalCrit/combat.store.js');
|
||||
|
||||
var MonsterCard = require('./monsterCard/monsterCard.jsx');
|
||||
|
||||
|
||||
|
||||
var attrMod = function(attr){
|
||||
return Math.floor(attr/2) - 5;
|
||||
}
|
||||
|
||||
var Encounter = React.createClass({
|
||||
mixins : [Store.mixin()],
|
||||
getInitialState: function() {
|
||||
return {
|
||||
enemies: this.createEnemies(this.props)
|
||||
};
|
||||
},
|
||||
|
||||
onStoreChange : function(){
|
||||
var players = Store.getplayersText();
|
||||
|
||||
},
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
name : '',
|
||||
desc : '',
|
||||
reward : '',
|
||||
enemies : [],
|
||||
players : '',
|
||||
unique : {},
|
||||
|
||||
monsterManual : {}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
this.setState({
|
||||
enemies : this.createEnemies(nextProps)
|
||||
})
|
||||
},
|
||||
|
||||
createEnemies : function(props){
|
||||
var self = this;
|
||||
return _.indexBy(_.map(props.enemies, function(type, index){
|
||||
return self.createEnemy(props, type, index)
|
||||
}), 'id')
|
||||
},
|
||||
|
||||
createEnemy : function(props, type, index){
|
||||
var stats = props.unique[type] || props.monsterManual[type];
|
||||
if(!stats) return;
|
||||
return _.extend({
|
||||
id : type + index,
|
||||
name : type,
|
||||
currentHP : stats.hp,
|
||||
initiative : _.random(1,20) + attrMod(stats.attr.dex)
|
||||
}, stats);
|
||||
},
|
||||
|
||||
|
||||
updateHP : function(enemyId, newHP){
|
||||
this.state.enemies[enemyId].currentHP = newHP;
|
||||
this.setState({
|
||||
enemies : this.state.enemies
|
||||
});
|
||||
},
|
||||
removeEnemy : function(enemyId){
|
||||
delete this.state.enemies[enemyId];
|
||||
this.setState({
|
||||
enemies : this.state.enemies
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
getPlayerObjects : function(){
|
||||
return _.reduce(this.props.players.split('\n'), function(r, line){
|
||||
var parts = line.split(' ');
|
||||
if(parts.length != 2) return r;
|
||||
r.push({
|
||||
name : parts[0],
|
||||
initiative : parts[1] * 1,
|
||||
isPC : true
|
||||
})
|
||||
return r;
|
||||
},[])
|
||||
},
|
||||
|
||||
|
||||
renderEnemies : function(){
|
||||
var self = this;
|
||||
|
||||
var sortedEnemies = _.sortBy(_.union(_.values(this.state.enemies), this.getPlayerObjects()), function(e){
|
||||
if(e && 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)}
|
||||
/>
|
||||
})
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
|
||||
var reward;
|
||||
if(this.props.reward){
|
||||
reward = <div className='reward'>
|
||||
<i className='fa fa-trophy' /> Rewards: {this.props.reward}
|
||||
</div>
|
||||
}
|
||||
|
||||
return(
|
||||
<div className='mainEncounter'>
|
||||
<div className='info'>
|
||||
<h1>{this.props.name}</h1>
|
||||
<p>{this.props.desc}</p>
|
||||
{reward}
|
||||
</div>
|
||||
|
||||
<div className='cardContainer'>
|
||||
{this.renderEnemies()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
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,36 +0,0 @@
|
||||
|
||||
.mainEncounter{
|
||||
box-sizing : border-box;
|
||||
overflow : hidden;
|
||||
width : auto;
|
||||
|
||||
&>.info{
|
||||
|
||||
margin-left: 10px;
|
||||
padding-bottom : 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
h1{
|
||||
font-size: 2em;
|
||||
font-weight: 800;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
p{
|
||||
margin-left: 10px;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.5em;
|
||||
max-width: 600px;
|
||||
}
|
||||
.reward{
|
||||
font-size: 0.8em;
|
||||
font-weight: 800;
|
||||
margin-top: 5px;
|
||||
i{
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var RollDice = require('naturalCrit/rollDice');
|
||||
|
||||
var AttackSlot = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
name : '',
|
||||
uses : null
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
lastRoll: {},
|
||||
usedCount : 0
|
||||
};
|
||||
},
|
||||
|
||||
rollDice : function(key, notation){
|
||||
var res = RollDice(notation);
|
||||
this.state.lastRoll[key] = res
|
||||
this.state.lastRoll[key + 'key'] = _.uniqueId(key);
|
||||
this.setState({
|
||||
lastRoll : this.state.lastRoll
|
||||
})
|
||||
},
|
||||
|
||||
renderUses : function(){
|
||||
var self = this;
|
||||
if(!this.props.uses) return null;
|
||||
|
||||
return _.times(this.props.uses, function(index){
|
||||
var atCount = index < self.state.usedCount;
|
||||
return <i
|
||||
key={index}
|
||||
className={cx('fa', {'fa-circle-o' : !atCount, 'fa-circle' : atCount})}
|
||||
onClick={self.updateCount.bind(self, atCount)}
|
||||
/>
|
||||
})
|
||||
},
|
||||
updateCount : function(used){
|
||||
this.setState({
|
||||
usedCount : this.state.usedCount + (used ? -1 : 1)
|
||||
});
|
||||
},
|
||||
|
||||
renderNotes : function(){
|
||||
var notes = _.omit(this.props, ['name', 'atk', 'dmg', 'uses', 'heal']);
|
||||
return _.map(notes, function(text, key){
|
||||
return <div key={key}>{key + ': ' + text}</div>
|
||||
});
|
||||
},
|
||||
|
||||
renderRolls : function(){
|
||||
var self = this;
|
||||
|
||||
return _.map(['atk', 'dmg', 'heal'], function(type){
|
||||
if(!self.props[type]) return null;
|
||||
return <div className={cx('roll', type)} key={type}>
|
||||
|
||||
<button onClick={self.rollDice.bind(self, type, self.props[type])}>
|
||||
<i className={cx('fa', {
|
||||
'fa-hand-grab-o' : type=='dmg',
|
||||
'fa-bullseye' : type=='atk',
|
||||
'fa-plus' : type=='heal'
|
||||
})} />
|
||||
{self.props[type]}
|
||||
</button>
|
||||
<span key={self.state.lastRoll[type+'key']}>{self.state.lastRoll[type]}</span>
|
||||
</div>
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className='attackSlot'>
|
||||
<div className='info'>
|
||||
<div className='name'>{this.props.name}</div>
|
||||
<div className='uses'>
|
||||
{this.renderUses()}
|
||||
</div>
|
||||
<div className='notes'>
|
||||
{this.renderNotes()}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className='rolls'>
|
||||
{this.renderRolls()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = AttackSlot;
|
||||
@@ -1,71 +0,0 @@
|
||||
|
||||
.attackSlot{
|
||||
//border : 1px solid black;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin-bottom : 5px;
|
||||
font-size : 0.8em;
|
||||
.info, .rolls{
|
||||
display : inline-block;
|
||||
vertical-align : top;
|
||||
}
|
||||
.info{
|
||||
width : 40%;
|
||||
.name{
|
||||
font-weight : 800;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.notes{
|
||||
font-size : 0.8em;
|
||||
}
|
||||
.uses{
|
||||
cursor : pointer;
|
||||
//font-size: 0.8em;
|
||||
//margin-top: 3px;
|
||||
}
|
||||
}
|
||||
.rolls{
|
||||
.roll{
|
||||
margin-bottom : 2px;
|
||||
&>span{
|
||||
font-weight: 800;
|
||||
.fadeInLeft();
|
||||
}
|
||||
button{
|
||||
width : 70px;
|
||||
margin-right : 5px;
|
||||
cursor : pointer;
|
||||
font-size : 0.7em;
|
||||
font-weight : 800;
|
||||
text-align : left;
|
||||
border : none;
|
||||
outline : 0;
|
||||
i{
|
||||
width : 15px;
|
||||
margin-right : 5px;
|
||||
border-right : 1px solid white;
|
||||
}
|
||||
&:hover{
|
||||
//text-align: right;
|
||||
}
|
||||
}
|
||||
&.atk{
|
||||
button{
|
||||
background-color : fade(@blue, 40%);
|
||||
i { border-color: @blue}
|
||||
}
|
||||
}
|
||||
&.dmg{
|
||||
button{
|
||||
background-color : fade(@red, 40%);
|
||||
i { border-color: @red}
|
||||
}
|
||||
}
|
||||
&.heal{
|
||||
button{
|
||||
background-color : fade(@green, 40%);
|
||||
i { border-color: @green}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var AttackSlot = require('./attackSlot/attackSlot.jsx');
|
||||
|
||||
var MonsterCard = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
name : '',
|
||||
hp : 1,
|
||||
currentHP : 1,
|
||||
ac: 1,
|
||||
move : 30,
|
||||
attr : {
|
||||
str : 8,
|
||||
con : 8,
|
||||
dex : 8,
|
||||
int : 8,
|
||||
wis : 8,
|
||||
cha : 8
|
||||
},
|
||||
attacks : {},
|
||||
spells : {},
|
||||
abilities : [],
|
||||
items : [],
|
||||
|
||||
updateHP : function(){},
|
||||
remove : function(){},
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
status : 'normal',
|
||||
usedItems : [],
|
||||
lastRoll : { },
|
||||
mousePos : null,
|
||||
tempHP : 0
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
window.addEventListener('mousemove', this.handleMouseDrag);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
|
||||
handleMouseDown : function(e){
|
||||
this.setState({
|
||||
mousePos : {
|
||||
x : e.pageX,
|
||||
y : e.pageY,
|
||||
}
|
||||
});
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
},
|
||||
handleMouseUp : function(e){
|
||||
if(!this.state.mousePos) return;
|
||||
|
||||
|
||||
this.props.updateHP(this.props.currentHP + this.state.tempHP);
|
||||
this.setState({
|
||||
mousePos : null,
|
||||
tempHP : 0
|
||||
});
|
||||
},
|
||||
|
||||
handleMouseDrag : function(e){
|
||||
if (!this.state.mousePos) return;
|
||||
var distance = Math.sqrt(Math.pow(e.pageX - this.state.mousePos.x, 2) + Math.pow(e.pageY - this.state.mousePos.y, 2));
|
||||
var mult = (e.pageY > this.state.mousePos.y ? -1 : 1)
|
||||
|
||||
this.setState({
|
||||
tempHP : Math.floor(distance * mult/25)
|
||||
})
|
||||
},
|
||||
|
||||
addUsed : function(item, shouldRemove){
|
||||
if(!shouldRemove) this.state.usedItems.push(item);
|
||||
if(shouldRemove) this.state.usedItems.splice(this.state.usedItems.indexOf(item), 1);
|
||||
|
||||
this.setState({
|
||||
usedItems : this.state.usedItems
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
renderHPBox : function(){
|
||||
var self = this;
|
||||
|
||||
var tempHP
|
||||
if(this.state.tempHP){
|
||||
var sign = (this.state.tempHP > 0 ? '+' : '');
|
||||
tempHP = <span className='tempHP'>{['(',sign,this.state.tempHP,')'].join('')}</span>
|
||||
}
|
||||
|
||||
return <div className='hpBox' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
||||
<div className='currentHP'>
|
||||
{tempHP} {this.props.currentHP}
|
||||
</div>
|
||||
{self.renderStats()}
|
||||
</div>
|
||||
},
|
||||
|
||||
renderStats : function(){
|
||||
var stats = {
|
||||
'fa fa-shield' : this.props.ac,
|
||||
//'fa fa-hourglass-2' : this.props.initiative,
|
||||
}
|
||||
return _.map(stats, function(val, icon){
|
||||
return <div className='stat' key={icon}> {val} <i className={icon} /></div>
|
||||
})
|
||||
},
|
||||
|
||||
renderAttacks : function(){
|
||||
var self = this;
|
||||
return _.map(this.props.attacks, function(attack, name){
|
||||
return <AttackSlot key={name} name={name} {...attack} />
|
||||
})
|
||||
},
|
||||
|
||||
renderSpells : function(){
|
||||
var self = this;
|
||||
return _.map(this.props.spells, function(spell, name){
|
||||
return <AttackSlot key={name} name={name} {...spell} />
|
||||
})
|
||||
},
|
||||
|
||||
renderAbilities : function(){
|
||||
return _.map(this.props.abilities, function(text, name){
|
||||
return <div className='ability' key={name}>
|
||||
<span className='name'>{name}</span>: {text}
|
||||
</div>
|
||||
});
|
||||
},
|
||||
|
||||
renderItems : function(){
|
||||
var self = this;
|
||||
var usedItems = this.state.usedItems.slice(0);
|
||||
return _.map(this.props.items, function(item, index){
|
||||
var used = _.contains(usedItems, item);
|
||||
if(used){
|
||||
usedItems.splice(usedItems.indexOf(item), 1);
|
||||
}
|
||||
return <span
|
||||
key={index}
|
||||
className={cx({'used' : used})}
|
||||
onClick={self.addUsed.bind(self, item, used)}>
|
||||
{item}
|
||||
</span>
|
||||
});
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
var condition = ''
|
||||
if(this.props.currentHP + this.state.tempHP > this.props.hp) condition='overhealed';
|
||||
if(this.props.currentHP + this.state.tempHP <= this.props.hp * 0.5) condition='hurt';
|
||||
if(this.props.currentHP + this.state.tempHP <= this.props.hp * 0.2) condition='last_legs';
|
||||
if(this.props.currentHP + this.state.tempHP <= 0) condition='dead';
|
||||
|
||||
|
||||
return(
|
||||
<div className={cx('monsterCard', condition)}>
|
||||
<div className='healthbar' style={{width : (this.props.currentHP + this.state.tempHP)/this.props.hp*100 + '%'}} />
|
||||
<div className='overhealbar' style={{width : (this.props.currentHP + this.state.tempHP - this.props.hp)/this.props.hp*100 + '%'}} />
|
||||
|
||||
|
||||
{this.renderHPBox()}
|
||||
<div className='info'>
|
||||
<span className='name'>{this.props.name}</span>
|
||||
</div>
|
||||
|
||||
<div className='attackContainer'>
|
||||
{this.renderAttacks()}
|
||||
</div>
|
||||
<div className='spellContainer'>
|
||||
{this.renderSpells()}
|
||||
</div>
|
||||
|
||||
<div className='abilitiesContainer'>
|
||||
{this.renderAbilities()}
|
||||
</div>
|
||||
<div className='itemContainer'>
|
||||
<i className='fa fa-flask' />
|
||||
{this.renderItems()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MonsterCard;
|
||||
|
||||
/*
|
||||
|
||||
|
||||
{this.props.initiative}
|
||||
|
||||
<i className='fa fa-times' onClick={this.props.remove} />
|
||||
*/
|
||||
@@ -1,129 +0,0 @@
|
||||
|
||||
@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 : 220px;
|
||||
margin : @marginSize;
|
||||
padding : 10px;
|
||||
background-color : white;
|
||||
border : 1px solid #bbb;
|
||||
.healthbar{
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
left : 0px;
|
||||
z-index : 50;
|
||||
height : 3px;
|
||||
max-width : 100%;
|
||||
background-color : @green;
|
||||
}
|
||||
.overhealbar{
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
left : 0px;
|
||||
z-index : 100;
|
||||
height : 3px;
|
||||
max-width : 100%;
|
||||
background-color : @blueLight;
|
||||
}
|
||||
&.hurt{
|
||||
.healthbar{
|
||||
background-color : orange;
|
||||
}
|
||||
}
|
||||
&.last_legs{
|
||||
background-color : lighten(@red, 49%);
|
||||
.healthbar{
|
||||
background-color : red;
|
||||
}
|
||||
}
|
||||
&.dead{
|
||||
opacity : 0.3;
|
||||
}
|
||||
&>.info{
|
||||
margin-bottom : 10px;
|
||||
.name{
|
||||
margin-right : 10px;
|
||||
font-size : 1.5em;
|
||||
}
|
||||
.stat{
|
||||
margin-right : 5px;
|
||||
font-size : 0.7em;
|
||||
i{
|
||||
font-size : 0.7em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.hpBox{
|
||||
.noselect();
|
||||
position : absolute;
|
||||
top : 5px;
|
||||
right : 5px;
|
||||
cursor : pointer;
|
||||
text-align : right;
|
||||
.currentHP{
|
||||
font-size : 2em;
|
||||
font-weight : 800;
|
||||
line-height : 0.8em;
|
||||
.tempHP{
|
||||
vertical-align : top;
|
||||
font-size : 0.4em;
|
||||
line-height : 0.8em;
|
||||
}
|
||||
}
|
||||
.stat{
|
||||
font-size : 0.8em;
|
||||
}
|
||||
.hpText{
|
||||
font-size : 0.6em;
|
||||
font-weight : 800;
|
||||
}
|
||||
}
|
||||
.abilitiesContainer{
|
||||
margin-top : 10px;
|
||||
.ability{
|
||||
font-size: 0.7em;
|
||||
.name{
|
||||
font-weight: 800;
|
||||
}
|
||||
}
|
||||
}
|
||||
.itemContainer{
|
||||
margin-top : 10px;
|
||||
i{
|
||||
font-size : 0.7em;
|
||||
}
|
||||
span{
|
||||
margin-right : 5px;
|
||||
cursor : pointer;
|
||||
font-size : 0.7em;
|
||||
&.used{
|
||||
text-decoration : line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var RollDice = require('naturalCrit/rollDice');
|
||||
|
||||
var DmDice = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
lastRoll:{ },
|
||||
diceNotation : {
|
||||
a : "1d20",
|
||||
b : "6d6 + 3",
|
||||
c : "1d20 - 1"
|
||||
}
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
roll : function(id){
|
||||
this.state.lastRoll[id] = RollDice(this.state.diceNotation[id]);
|
||||
this.setState({
|
||||
lastRoll : this.state.lastRoll
|
||||
});
|
||||
},
|
||||
handleChange : function(id, e){
|
||||
this.state.diceNotation[id] = e.target.value;
|
||||
this.setState({
|
||||
diceNotation : this.state.diceNotation
|
||||
});
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
renderRolls : function(){
|
||||
var self = this;
|
||||
return _.map(['a', 'b', 'c'], function(id){
|
||||
return <div className='roll' key={id} onClick={self.roll.bind(self, id)}>
|
||||
<input type="text" value={self.state.diceNotation[id]} onChange={self.handleChange.bind(self, id)} />
|
||||
<i className='fa fa-random' />
|
||||
<span key={self.state.lastRoll[id]}>{self.state.lastRoll[id]}</span>
|
||||
</div>
|
||||
})
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className='dmDice'>
|
||||
<h3> <i className='fa fa-random' /> DM Dice </h3>
|
||||
{this.renderRolls()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = DmDice;
|
||||
@@ -1,32 +0,0 @@
|
||||
.dmDice{
|
||||
|
||||
h3{
|
||||
color : white;
|
||||
background-color: @teal;
|
||||
}
|
||||
|
||||
.roll{
|
||||
cursor: pointer;
|
||||
.noselect();
|
||||
input[type="text"]{
|
||||
margin-left: 10px;
|
||||
margin-bottom: 6px;
|
||||
margin-top: 6px;
|
||||
width : 60px;
|
||||
font-family: monospace;
|
||||
padding : 5px;
|
||||
}
|
||||
i.fa-random{
|
||||
font-size: 0.8em;
|
||||
margin: 0 10px;
|
||||
}
|
||||
span{
|
||||
font-weight: 800;
|
||||
.fadeInLeft();
|
||||
}
|
||||
|
||||
&:hover{
|
||||
background-color: fade(@teal, 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var JSONFileEditor = require('naturalCrit/jsonFileEditor/jsonFileEditor.jsx');
|
||||
|
||||
//var GetRandomEncounter = require('naturalCrit/randomEncounter.js');
|
||||
|
||||
var Store = require('naturalCrit/combat.store.js');
|
||||
var Actions = require('naturalCrit/combat.actions.js');
|
||||
|
||||
|
||||
var Encounters = React.createClass({
|
||||
mixins : [Store.mixin()],
|
||||
onStoreChange : function(){
|
||||
this.setState({
|
||||
encounters : Store.getEncounters(),
|
||||
selectedEncounter : Store.getSelectedEncounterIndex()
|
||||
});
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
encounters : Store.getEncounters(),
|
||||
selectedEncounter : Store.getSelectedEncounterIndex()
|
||||
};
|
||||
},
|
||||
/*
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
encounters : [],
|
||||
selectedEncounter : 0,
|
||||
|
||||
onJSONChange : function(encounterIndex, json){},
|
||||
onSelectEncounter : function(encounterIndex){},
|
||||
onRemoveEncounter : function(encounterIndex){}
|
||||
};
|
||||
},
|
||||
*/
|
||||
handleJSONChange : function(encounterIndex, json){
|
||||
//this.props.onJSONChange(encounterIndex, json);
|
||||
Actions.updateEncounter(encounterIndex, json);
|
||||
},
|
||||
handleSelectEncounter : function(encounterIndex){
|
||||
//this.props.onSelectEncounter(encounterIndex);
|
||||
Actions.selectEncounter(encounterIndex);
|
||||
},
|
||||
handleRemoveEncounter : function(encounterIndex){
|
||||
//this.props.onRemoveEncounter(encounterIndex);
|
||||
Actions.removeEncounter(encounterIndex);
|
||||
},
|
||||
addRandomEncounter : function(){
|
||||
Actions.addEncounter();
|
||||
},
|
||||
|
||||
|
||||
renderEncounters : function(){
|
||||
var self = this;
|
||||
return _.map(this.state.encounters, function(encounter, index){
|
||||
|
||||
var isSelected = self.state.selectedEncounter == index;
|
||||
return <div className={cx('encounter' , {'selected' : isSelected})} key={index}>
|
||||
|
||||
<i onClick={self.handleSelectEncounter.bind(self, index)} className={cx('select', 'fa', {
|
||||
'fa-square-o' : !isSelected,
|
||||
'fa-check-square-o' : isSelected,
|
||||
})} />
|
||||
|
||||
|
||||
<JSONFileEditor
|
||||
name={encounter.name}
|
||||
json={encounter}
|
||||
onJSONChange={self.handleJSONChange.bind(self, index)}
|
||||
/>
|
||||
|
||||
<i onClick={self.handleRemoveEncounter.bind(self, index)} className='remove fa fa-times' />
|
||||
</div>
|
||||
})
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className='encounters'>
|
||||
<h3>
|
||||
<i className='fa fa-flag' /> Encounters
|
||||
<button className='addEncounter'>
|
||||
<i className='fa fa-plus' onClick={this.addRandomEncounter}/>
|
||||
</button>
|
||||
</h3>
|
||||
{this.renderEncounters()}
|
||||
|
||||
<div className='controls'>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Encounters;
|
||||
@@ -1,53 +0,0 @@
|
||||
|
||||
.encounters{
|
||||
margin-bottom : 20px;
|
||||
h3{
|
||||
background-color : @red;
|
||||
color : white;
|
||||
button{
|
||||
.animate(color);
|
||||
float : right;
|
||||
cursor : pointer;
|
||||
background-color : transparent;
|
||||
border : none;
|
||||
outline : none;
|
||||
&:hover{
|
||||
color : white;
|
||||
}
|
||||
}
|
||||
}
|
||||
.encounter{
|
||||
position : relative;
|
||||
padding-left : 15px;
|
||||
border-left : 0px solid @teal;
|
||||
.animateAll();
|
||||
&:hover{
|
||||
i.remove{
|
||||
opacity : 1;
|
||||
}
|
||||
}
|
||||
i.remove{
|
||||
.animate(opacity);
|
||||
position : absolute;
|
||||
top : 3px;
|
||||
right : 3px;
|
||||
cursor : pointer;
|
||||
opacity : 0;
|
||||
font-size : 0.6em;
|
||||
color : #333;
|
||||
&:hover{
|
||||
color : @red;
|
||||
}
|
||||
}
|
||||
i.select{
|
||||
cursor : pointer;
|
||||
}
|
||||
.jsonFileEditor{
|
||||
display : inline-block;
|
||||
}
|
||||
&.selected{
|
||||
//background-color : fade(@green, 30%);
|
||||
border-left : 8px solid @teal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var JSONFileEditor = require('naturalCrit/jsonFileEditor/jsonFileEditor.jsx');
|
||||
|
||||
var DMDice = require('./dmDice/dmDice.jsx');
|
||||
var Encounters = require('./encounters/encounters.jsx');
|
||||
|
||||
var Store = require('naturalCrit/combat.store.js');
|
||||
var Actions = require('naturalCrit/combat.actions.js');
|
||||
|
||||
|
||||
var Sidebar = React.createClass({
|
||||
mixins : [Store.mixin()],
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
hide : false,
|
||||
|
||||
monsterManual : Store.getMonsterManual(),
|
||||
players : Store.getPlayersText()
|
||||
};
|
||||
},
|
||||
|
||||
onStoreChange : function(){
|
||||
this.setState({
|
||||
players : Store.getPlayersText(),
|
||||
monsterManual : Store.getMonsterManual()
|
||||
})
|
||||
},
|
||||
|
||||
handleLogoClick : function(){
|
||||
this.setState({
|
||||
hide : !this.state.hide
|
||||
})
|
||||
},
|
||||
handleMonsterManualChange : function(json){
|
||||
Actions.updateMonsterManual(json);
|
||||
},
|
||||
handlePlayerChange : function(e){
|
||||
Actions.updatePlayers(e.target.value);
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className={cx('sidebar', {'hide' : this.state.hide})}>
|
||||
<div className='logo'>
|
||||
<svg onClick={this.handleLogoClick} version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100"><path d="M80.644,87.982l16.592-41.483c0.054-0.128,0.088-0.26,0.108-0.394c0.006-0.039,0.007-0.077,0.011-0.116 c0.007-0.087,0.008-0.174,0.002-0.26c-0.003-0.046-0.007-0.091-0.014-0.137c-0.014-0.089-0.036-0.176-0.063-0.262 c-0.012-0.034-0.019-0.069-0.031-0.103c-0.047-0.118-0.106-0.229-0.178-0.335c-0.004-0.006-0.006-0.012-0.01-0.018L67.999,3.358 c-0.01-0.013-0.003-0.026-0.013-0.04L68,3.315V4c0,0-0.033,0-0.037,0c-0.403-1-1.094-1.124-1.752-0.976 c0,0.004-0.004-0.012-0.007-0.012C66.201,3.016,66.194,3,66.194,3H66.19h-0.003h-0.003h-0.004h-0.003c0,0-0.004,0-0.007,0 s-0.003-0.151-0.007-0.151L20.495,15.227c-0.025,0.007-0.046-0.019-0.071-0.011c-0.087,0.028-0.172,0.041-0.253,0.083 c-0.054,0.027-0.102,0.053-0.152,0.085c-0.051,0.033-0.101,0.061-0.147,0.099c-0.044,0.036-0.084,0.073-0.124,0.113 c-0.048,0.048-0.093,0.098-0.136,0.152c-0.03,0.039-0.059,0.076-0.085,0.117c-0.046,0.07-0.084,0.145-0.12,0.223 c-0.011,0.023-0.027,0.042-0.036,0.066L2.911,57.664C2.891,57.715,3,57.768,3,57.82v0.002c0,0.186,0,0.375,0,0.562 c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004v0.002c0,0.074-0.002,0.15,0.012,0.223 C3.015,58.631,3,58.631,3,58.633c0,0.004,0,0.004,0,0.008c0,0,0,0,0,0.002c0,0,0,0,0,0.004v0.004c0,0,0,0,0,0.002v0.004 c0,0.191-0.046,0.377,0.06,0.545c0-0.002-0.03,0.004-0.03,0.004c0,0.004-0.03,0.004-0.03,0.004c0,0.002,0,0.002,0,0.002 l-0.045,0.004c0.03,0.047,0.036,0.09,0.068,0.133l29.049,37.359c0.002,0.004,0,0.006,0.002,0.01c0.002,0.002,0,0.004,0.002,0.008 c0.006,0.008,0.014,0.014,0.021,0.021c0.024,0.029,0.052,0.051,0.078,0.078c0.027,0.029,0.053,0.057,0.082,0.082 c0.03,0.027,0.055,0.062,0.086,0.088c0.026,0.02,0.057,0.033,0.084,0.053c0.04,0.027,0.081,0.053,0.123,0.076 c0.005,0.004,0.01,0.008,0.016,0.01c0.087,0.051,0.176,0.09,0.269,0.123c0.042,0.014,0.082,0.031,0.125,0.043 c0.021,0.006,0.041,0.018,0.062,0.021c0.123,0.027,0.249,0.043,0.375,0.043c0.099,0,0.202-0.012,0.304-0.027l45.669-8.303 c0.057-0.01,0.108-0.021,0.163-0.037C79.547,88.992,79.562,89,79.575,89c0.004,0,0.004,0,0.004,0c0.021,0,0.039-0.027,0.06-0.035 c0.041-0.014,0.08-0.034,0.12-0.052c0.021-0.01,0.044-0.019,0.064-0.03c0.017-0.01,0.026-0.015,0.033-0.017 c0.014-0.008,0.023-0.021,0.037-0.028c0.14-0.078,0.269-0.174,0.38-0.285c0.014-0.016,0.024-0.034,0.038-0.048 c0.109-0.119,0.201-0.252,0.271-0.398c0.006-0.01,0.016-0.018,0.021-0.029c0.004-0.008,0.008-0.017,0.011-0.026 c0.002-0.004,0.003-0.006,0.005-0.01C80.627,88.021,80.635,88.002,80.644,87.982z M77.611,84.461L48.805,66.453l32.407-25.202 L77.611,84.461z M46.817,63.709L35.863,23.542l43.818,14.608L46.817,63.709z M84.668,40.542l8.926,5.952l-11.902,29.75 L84.668,40.542z M89.128,39.446L84.53,36.38l-6.129-12.257L89.128,39.446z M79.876,34.645L37.807,20.622L65.854,6.599L79.876,34.645 z M33.268,19.107l-6.485-2.162l23.781-6.487L33.268,19.107z M21.92,18.895l8.67,2.891L10.357,47.798L21.92,18.895z M32.652,24.649 l10.845,39.757L7.351,57.178L32.652,24.649z M43.472,67.857L32.969,92.363L8.462,60.855L43.472,67.857z M46.631,69.09l27.826,17.393 l-38.263,6.959L46.631,69.09z"></path></svg>
|
||||
<span className='name'>
|
||||
<div>Natural<span className='crit'>Crit</span></div>
|
||||
<small>Combat Manager</small>
|
||||
</span>
|
||||
</div>
|
||||
<div className='contents'>
|
||||
<div className='monsterManualContainer'>
|
||||
<JSONFileEditor
|
||||
name="Monster Manual"
|
||||
json={this.state.monsterManual}
|
||||
onJSONChange={this.handleMonsterManualChange}
|
||||
/>
|
||||
</div>
|
||||
<Encounters />
|
||||
<div className='addPlayers'>
|
||||
<h3> <i className='fa fa-group' /> Players </h3>
|
||||
<textarea value={this.state.players} onChange={this.handlePlayerChange} />
|
||||
</div>
|
||||
<DMDice />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Sidebar;
|
||||
@@ -1,90 +0,0 @@
|
||||
|
||||
@font-face {
|
||||
font-family : CodeLight;
|
||||
src : url('/assets/naturalCrit/sidebar/CODE Light.otf');
|
||||
}
|
||||
@font-face {
|
||||
font-family : CodeBold;
|
||||
src : url('/assets/naturalCrit/sidebar/CODE Bold.otf');
|
||||
}
|
||||
.sidebar{
|
||||
.animateAll();
|
||||
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;
|
||||
opacity : 0;
|
||||
}
|
||||
.contents{
|
||||
height : 0px;
|
||||
opacity : 0;
|
||||
}
|
||||
}
|
||||
.logo{
|
||||
padding : 10px 10px;
|
||||
background-color : @steel;
|
||||
font-family : 'CodeLight', sans-serif;
|
||||
font-size : 1.8em;
|
||||
color : white;
|
||||
svg{
|
||||
vertical-align : middle;
|
||||
height : 1em;
|
||||
margin-right : 0.2em;
|
||||
cursor : pointer;
|
||||
fill : white;
|
||||
}
|
||||
|
||||
span.name{
|
||||
.animateAll();
|
||||
position : absolute;
|
||||
top : 15px;
|
||||
left : 50px;
|
||||
opacity : 1;
|
||||
font-size: 0.9em;
|
||||
line-height: 0.5em;
|
||||
span.crit{
|
||||
font-family : 'CodeBold';
|
||||
}
|
||||
small{
|
||||
font-size: 0.3em;
|
||||
font-family : 'Open Sans';
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
.contents{
|
||||
.animate(opacity);
|
||||
box-sizing : border-box;
|
||||
width : 100%;
|
||||
&>*{
|
||||
width : 100%;
|
||||
}
|
||||
h3{
|
||||
padding : 10px;
|
||||
font-size : 0.8em;
|
||||
font-weight : 800;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
.addPlayers{
|
||||
h3{
|
||||
color : white;
|
||||
background-color: @purple;
|
||||
}
|
||||
textarea{
|
||||
height : 80px;
|
||||
width : 100px;
|
||||
margin : 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,52 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
|
||||
var Router = require('pico-router');
|
||||
var Icon = require('naturalCrit/icon.svg.jsx');
|
||||
var Logo = require('naturalCrit/logo/logo.jsx');
|
||||
|
||||
|
||||
var HomebrewIcon = require('naturalCrit/homebrewIcon.svg.jsx');
|
||||
var CombatIcon = require('naturalCrit/combatIcon.svg.jsx');
|
||||
|
||||
var Home = React.createClass({
|
||||
|
||||
navigate : function(){
|
||||
|
||||
},
|
||||
|
||||
render : function(){
|
||||
var self = this;
|
||||
return(
|
||||
<div className='home'>
|
||||
|
||||
|
||||
<div className='top'>
|
||||
<Logo />
|
||||
<p>Top-tier tools for the discerning DM</p>
|
||||
</div>
|
||||
|
||||
<div className='tools'>
|
||||
|
||||
<div className='homebrew toolContainer' onClick={Router.navigate.bind(self, '/homebrew')}>
|
||||
<div className='content'>
|
||||
<HomebrewIcon />
|
||||
<h2>The Homebrewery</h2>
|
||||
<p>Make authentic-looking 5e homebrews using Markdown</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className='combat toolContainer underConstruction' onClick={Router.navigate.bind(self, '/combat')}>
|
||||
<div className='content'>
|
||||
<CombatIcon />
|
||||
<h2>Combat Manager</h2>
|
||||
<p>Easily create and manage complex encouters for your party</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Home;
|
||||
@@ -1,166 +0,0 @@
|
||||
|
||||
.home{
|
||||
height : 100vh;
|
||||
background-color : white;
|
||||
.top{
|
||||
.fadeInTop(1s);
|
||||
.delay(0.5);
|
||||
margin-bottom : 100px;
|
||||
padding-top : 100px;
|
||||
text-align : center;
|
||||
.logo{
|
||||
font-size : 4em;
|
||||
color : black;
|
||||
svg{
|
||||
fill : black;
|
||||
}
|
||||
}
|
||||
p{
|
||||
margin-top : 10px;
|
||||
font-size : 1.3em;
|
||||
font-style : italic;
|
||||
color : @grey;
|
||||
}
|
||||
}
|
||||
.tools{
|
||||
width : 100%;
|
||||
text-align : center;
|
||||
.toolContainer{
|
||||
.sequentialDelay(0.5s, 1s);
|
||||
.fadeInDown(1s);
|
||||
.keep();
|
||||
display : inline-block;
|
||||
cursor : pointer;
|
||||
opacity : 0;
|
||||
text-align : center;
|
||||
border-right : 1px solid #333;
|
||||
&:last-child{
|
||||
border : none;
|
||||
}
|
||||
.content{
|
||||
.addSketch(360px);
|
||||
.animateAll(0.5s);
|
||||
position : relative;
|
||||
width : 500px;
|
||||
padding : 40px;
|
||||
&:hover{
|
||||
svg, h2{
|
||||
.transform(scale(1.3));
|
||||
}
|
||||
}
|
||||
h2{
|
||||
.animateAll(0.5s);
|
||||
font-family : 'CodeBold';
|
||||
font-size : 2em;
|
||||
}
|
||||
p{
|
||||
max-width : 300px;
|
||||
margin : 20px auto;
|
||||
line-height : 1.5em;
|
||||
}
|
||||
svg{
|
||||
.animateAll(0.5s);
|
||||
height : 10em;
|
||||
}
|
||||
}
|
||||
//Proejct specific styles
|
||||
&.homebrew{
|
||||
.content:hover{
|
||||
background-color : fade(@teal, 20%);
|
||||
}
|
||||
}
|
||||
&.combat{
|
||||
.content:hover{
|
||||
background-color : fade(@red, 20%);
|
||||
}
|
||||
}
|
||||
//Under Construction styles
|
||||
&.underConstruction{
|
||||
cursor : initial;
|
||||
.content{
|
||||
&:hover{
|
||||
svg, h2{
|
||||
.transform(scale(1.0));
|
||||
}
|
||||
}
|
||||
svg, h2{
|
||||
opacity : 0.3;
|
||||
}
|
||||
&:after{
|
||||
.animateAll();
|
||||
content : "Under Construction";
|
||||
position : absolute;
|
||||
display : block;
|
||||
top : 120px;
|
||||
left : 0px;
|
||||
width : 100%;
|
||||
padding : 10px 0px;
|
||||
//opacity : 0;
|
||||
background-color : fade(@grey, 50%);
|
||||
font-size : 2em;
|
||||
font-weight : 800;
|
||||
text-align : center;
|
||||
text-transform : uppercase;
|
||||
}
|
||||
&:before{
|
||||
.rumble(6s);
|
||||
content : "";
|
||||
position : absolute;
|
||||
display : block;
|
||||
top : 130px;
|
||||
right : 30px;
|
||||
height : 50px;
|
||||
width : 40px;
|
||||
//opacity : 0;
|
||||
background-image : url('/assets/naturalCrit/home/bulldozer.png');
|
||||
background-repeat : no-repeat;
|
||||
background-size : contain;
|
||||
animation-iteration-count : infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.addSketch(@length, @color : black){
|
||||
path, line, polyline, circle, rect, polygon {
|
||||
.sketch(@length, @color, 4s);
|
||||
stroke-dasharray : @length;
|
||||
stroke-dashoffset : 0px;
|
||||
stroke : @color;
|
||||
stroke-width : 0.5px;
|
||||
fill : @color;
|
||||
//.animateAll(3s);
|
||||
}
|
||||
}
|
||||
.sketch(@length, @color : black, @duration : 3s, @easing : @defaultEasing){
|
||||
.createAnimation(sketch, @duration, @easing);
|
||||
.sketchKeyFrames(){
|
||||
0% { stroke-dashoffset : @length; fill: transparent;}
|
||||
50% { stroke-dashoffset : @length; fill: transparent;}
|
||||
80% { stroke-dashoffset : 0px; fill: transparent;}
|
||||
100% { stroke-dashoffset : 0px; fill:@color;}
|
||||
}
|
||||
@-webkit-keyframes sketch {.sketchKeyFrames();}
|
||||
@-moz-keyframes sketch {.sketchKeyFrames();}
|
||||
@-ms-keyframes sketch {.sketchKeyFrames();}
|
||||
@-o-keyframes sketch {.sketchKeyFrames();}
|
||||
@keyframes sketch {.sketchKeyFrames();}
|
||||
}
|
||||
/*
|
||||
.sketch(@length, @color : black, @duration : 3s, @easing : @defaultEasing){
|
||||
.createAnimation(bounce, @duration, @easing);
|
||||
.sketchKeyFrames(){
|
||||
0% { stroke-dashoffset : 0px; fill:@color;}
|
||||
15% { stroke-dashoffset : 0px; fill : transparent}
|
||||
50% { stroke-dashoffset : @length; fill: transparent}
|
||||
85% { stroke-dashoffset : 0px; fill:transparent;}
|
||||
100% { stroke-dashoffset : 0px; fill:@color;}
|
||||
}
|
||||
@-webkit-keyframes bounce {.sketchKeyFrames();}
|
||||
@-moz-keyframes bounce {.sketchKeyFrames();}
|
||||
@-ms-keyframes bounce {.sketchKeyFrames();}
|
||||
@-o-keyframes bounce {.sketchKeyFrames();}
|
||||
@keyframes bounce {.sketchKeyFrames();}
|
||||
}
|
||||
*/
|
||||
@@ -1,36 +0,0 @@
|
||||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var cx = require('classnames');
|
||||
var CreateRouter = require('pico-router').createRouter;
|
||||
|
||||
var Home = require('./home/home.jsx');
|
||||
var CombatManager = require('./combatManager/combatManager.jsx');
|
||||
//var Homebrew = require('./homebrew/homebrew.jsx');
|
||||
|
||||
|
||||
var Router = CreateRouter({
|
||||
'/' : <Home />,
|
||||
'/combat' : <CombatManager />,
|
||||
//'/homebrew' : <Homebrew />,
|
||||
});
|
||||
|
||||
|
||||
var NaturalCrit = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
url : '/'
|
||||
};
|
||||
},
|
||||
|
||||
render : function(){
|
||||
return <div className='naturalCrit'>
|
||||
<Router initialUrl={this.props.url} />
|
||||
</div>
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = NaturalCrit;
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
@import 'naturalCrit/styles/reset.less';
|
||||
//@import 'naturalCrit/styles/elements.less';
|
||||
@import 'naturalCrit/styles/animations.less';
|
||||
@import 'naturalCrit/styles/colors.less';
|
||||
@import 'naturalCrit/styles/tooltip.less';
|
||||
|
||||
@import 'font-awesome/css/font-awesome.css';
|
||||
|
||||
html,body, #reactContainer, .naturalCrit{
|
||||
min-height : 100%;
|
||||
}
|
||||
|
||||
@sidebarWidth : 250px;
|
||||
|
||||
body{
|
||||
background-color : #eee;
|
||||
font-family : 'Open Sans', sans-serif;
|
||||
color : #4b5055;
|
||||
font-weight : 100;
|
||||
text-rendering : optimizeLegibility;
|
||||
margin : 0;
|
||||
padding : 0;
|
||||
height : 100%;
|
||||
}
|
||||
|
||||
.naturalCrit{
|
||||
color : #333;
|
||||
background-color: #eee;
|
||||
|
||||
}
|
||||
|
||||
.noselect(){
|
||||
-webkit-touch-callout : none;
|
||||
-webkit-user-select : none;
|
||||
-khtml-user-select : none;
|
||||
-moz-user-select : none;
|
||||
-ms-user-select : none;
|
||||
user-select : none;
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script>global=window</script>
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||
<link rel="icon" href="/assets/naturalCrit/favicon.ico" type="image/x-icon" />
|
||||
{{=vitreum.css}}
|
||||
{{=vitreum.globals}}
|
||||
<title>Natural Crit - D&D Tools</title>
|
||||
|
||||
{{? vitreum.inProduction}}
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-72212009-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
{{?}}
|
||||
</head>
|
||||
<body>
|
||||
<div id="reactContainer">{{=vitreum.component}}</div>
|
||||
</body>
|
||||
{{=vitreum.libs}}
|
||||
{{=vitreum.js}}
|
||||
{{=vitreum.reactRender}}
|
||||
</html>
|
||||
22
client/template.js
Normal file
22
client/template.js
Normal file
@@ -0,0 +1,22 @@
|
||||
module.exports = function(vitreum){
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link href="//fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
|
||||
<link rel="icon" href="/assets/homebrew/favicon.ico" type="image/x-icon" />
|
||||
|
||||
<title>The Homebrewery - NaturalCrit</title>
|
||||
${vitreum.head}
|
||||
</head>
|
||||
<body>
|
||||
<main id="reactRoot">${vitreum.body}</main>
|
||||
</body>
|
||||
${vitreum.js}
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
10
config/default.json
Normal file
10
config/default.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"log_level" : "info",
|
||||
"login_path" : "/dev/login",
|
||||
"jwt_secret" : "secretsecret",
|
||||
"admin" : {
|
||||
"user" : "admin",
|
||||
"pass" : "password",
|
||||
"key" : "adminadminadmin"
|
||||
}
|
||||
}
|
||||
4
config/production.json
Normal file
4
config/production.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"login_path" : "http://naturalcrit.com/login",
|
||||
"log_level" : "warn"
|
||||
}
|
||||
4
config/staging.json
Normal file
4
config/staging.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"login_path" : "http://staging.naturalcrit.com/login",
|
||||
"log_level" : "trace"
|
||||
}
|
||||
50
gulpfile.js
50
gulpfile.js
@@ -1,50 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var vitreumTasks = require("vitreum/tasks");
|
||||
var gulp = require("gulp");
|
||||
|
||||
|
||||
var gulp = vitreumTasks(gulp, {
|
||||
entryPoints: ["./client/naturalCrit", "./client/homebrew", "./client/admin"],
|
||||
|
||||
DEV: true,
|
||||
|
||||
buildPath: "./build/",
|
||||
pageTemplate: "./client/template.dot",
|
||||
|
||||
projectModules: ["./shared/naturalCrit"],
|
||||
|
||||
additionalRequirePaths : ['./shared'],
|
||||
|
||||
assetExts: ["*.svg", "*.png", "*.jpg", "*.pdf", "*.eot", "*.otf", "*.woff", "*.woff2", "*.ico", "*.ttf"],
|
||||
|
||||
serverWatchPaths: ["server"],
|
||||
serverScript: "server.js",
|
||||
libs: [
|
||||
"react",
|
||||
"react-dom",
|
||||
"lodash",
|
||||
"classnames",
|
||||
"jsoneditor",
|
||||
|
||||
"moment",
|
||||
|
||||
"superagent",
|
||||
|
||||
"marked",
|
||||
"pico-router",
|
||||
"pico-flux"
|
||||
],
|
||||
clientLibs: [],
|
||||
});
|
||||
|
||||
|
||||
var rename = require('gulp-rename');
|
||||
var less = require('gulp-less');
|
||||
gulp.task('phb', function(){
|
||||
gulp.src('./client/homebrew/phbStyle/phb.style.less')
|
||||
.pipe(less())
|
||||
.pipe(rename('phb.standalone.css'))
|
||||
.pipe(gulp.dest('./'));
|
||||
})
|
||||
|
||||
21
license
Normal file
21
license
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Scott Tolksdorf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
58
package.json
58
package.json
@@ -1,31 +1,55 @@
|
||||
{
|
||||
"name": "naturalCrit",
|
||||
"description": "A super rad project!",
|
||||
"version": "0.0.0",
|
||||
"name": "homebrewery",
|
||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||
"version": "3.0.0",
|
||||
"scripts": {
|
||||
"postinstall": "gulp prod",
|
||||
"start": "node server.js"
|
||||
"logs": "heroku logs -t --app=homebrewery",
|
||||
"dev": "node scripts/dev.js",
|
||||
"quick": "node scripts/quick.js",
|
||||
"build": "node scripts/build.js",
|
||||
"populate": "node scripts/populate.js",
|
||||
"prod": "set NODE_ENV=production&& npm run build",
|
||||
"postinstall": "npm run build",
|
||||
"start": "node server.js",
|
||||
"snippet": "nodemon scripts/snippet.test.js",
|
||||
"todo": "./node_modules/.bin/fixme -i node_modules/** -i build/**",
|
||||
"test": "mocha tests",
|
||||
"test:dev": "nodemon -x mocha tests || exit 0",
|
||||
"test:markdown": "nodemon -x mocha tests/markdown.test.js || exit 0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "BSD-2-Clause",
|
||||
"author": "stolksdorf",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"app-module-path": "^1.0.4",
|
||||
"basic-auth": "^1.0.3",
|
||||
"body-parser": "^1.14.2",
|
||||
"classnames": "^2.2.0",
|
||||
"codemirror": "^5.22.0",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"egads": "^1.0.1",
|
||||
"express": "^4.13.3",
|
||||
"gulp": "^3.9.0",
|
||||
"jsoneditor": "^4.2.1",
|
||||
"lodash": "^3.10.1",
|
||||
"marked": "^0.3.5",
|
||||
"jwt-simple": "^0.5.1",
|
||||
"lodash": "^4.17.3",
|
||||
"loglevel": "^1.4.1",
|
||||
"moment": "^2.11.0",
|
||||
"mongoose": "^4.3.3",
|
||||
"pico-flux": "^1.1.0",
|
||||
"pico-router": "^1.0.0",
|
||||
"react": "^0.14.2",
|
||||
"react-dom": "^0.14.2",
|
||||
"nconf": "^0.8.4",
|
||||
"pico-flux": "^2.1.2",
|
||||
"pico-router": "^1.1.0",
|
||||
"react": "^15.4.1",
|
||||
"react-dom": "^15.4.1",
|
||||
"shortid": "^2.2.4",
|
||||
"striptags": "^2.1.1",
|
||||
"superagent": "^1.6.1",
|
||||
"vitreum": "^3.1.1"
|
||||
"vitreum": "^4.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"app-module-path": "^2.1.0",
|
||||
"chai": "^3.5.0",
|
||||
"chai-as-promised": "^6.0.0",
|
||||
"chai-subset": "^1.4.0",
|
||||
"fixme": "^0.4.3",
|
||||
"mocha": "^3.2.0",
|
||||
"supertest": "^2.0.1",
|
||||
"supertest-as-promised": "^4.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
21
scripts/build.js
Normal file
21
scripts/build.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const label = 'build';
|
||||
console.time(label);
|
||||
|
||||
const clean = require('vitreum/steps/clean.js');
|
||||
const jsx = require('vitreum/steps/jsx.js');
|
||||
const lib = require('vitreum/steps/libs.js');
|
||||
const less = require('vitreum/steps/less.js');
|
||||
const asset = require('vitreum/steps/assets.js');
|
||||
|
||||
const Proj = require('./project.json');
|
||||
|
||||
Promise.resolve()
|
||||
.then(()=>clean())
|
||||
.then(()=>lib(Proj.libs))
|
||||
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, ['./shared']))
|
||||
.then((deps)=>less('homebrew', ['./shared'], deps))
|
||||
.then(()=>jsx('admin', './client/admin/admin.jsx', Proj.libs, ['./shared']))
|
||||
.then((deps)=>less('admin', ['./shared'], deps))
|
||||
.then(()=>asset(Proj.assets, ['./shared', './client']))
|
||||
.then(()=>console.timeEnd.bind(console, label))
|
||||
.catch(console.error);
|
||||
21
scripts/dev.js
Normal file
21
scripts/dev.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const label = 'dev';
|
||||
console.time(label);
|
||||
|
||||
const jsx = require('vitreum/steps/jsx.watch.js');
|
||||
const less = require('vitreum/steps/less.watch.js');
|
||||
const assets = require('vitreum/steps/assets.watch.js');
|
||||
const server = require('vitreum/steps/server.watch.js');
|
||||
const livereload = require('vitreum/steps/livereload.js');
|
||||
|
||||
const Proj = require('./project.json');
|
||||
|
||||
Promise.resolve()
|
||||
.then(()=>jsx('homebrew', './client/homebrew/homebrew.jsx', Proj.libs, './shared'))
|
||||
.then((deps)=>less('homebrew', './shared', deps))
|
||||
.then(()=>jsx('admin', './client/admin/admin.jsx', Proj.libs, './shared'))
|
||||
.then((deps)=>less('admin', './shared', deps))
|
||||
.then(()=>assets(Proj.assets, ['./shared', './client']))
|
||||
.then(()=>livereload())
|
||||
.then(()=>server('./server.js', ['server']))
|
||||
.then(()=>console.timeEnd.bind(console, label))
|
||||
.catch(console.error)
|
||||
8
scripts/notes.js
Normal file
8
scripts/notes.js
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
require('fixme')({
|
||||
path: process.cwd(),
|
||||
ignored_directories: ['node_modules/**', '.git/**', 'build/**'],
|
||||
file_patterns: ['**/*.js', '**/*.jsx', '**/*.less'],
|
||||
file_encoding: 'utf8',
|
||||
line_length_limit: 200
|
||||
});
|
||||
26
scripts/populate.js
Normal file
26
scripts/populate.js
Normal file
@@ -0,0 +1,26 @@
|
||||
//Populates the DB with a bunch of brews for UI testing
|
||||
const _ = require('lodash');
|
||||
|
||||
const DB = require('../server/db.js');
|
||||
const BrewData = require('../server/brew.data.js');
|
||||
//const BrewGen = require('../tests/brew.gen.js');
|
||||
|
||||
const BrewGen = require('../shared/homebrewery/snippets/brew/brew.snippet.js');
|
||||
|
||||
const BREW_COUNT = 50;
|
||||
|
||||
return Promise.resolve()
|
||||
.then(DB.connect)
|
||||
.then(BrewData.removeAll)
|
||||
.then(() => {
|
||||
return _.reduce(_.times(BREW_COUNT, BrewGen.brewModel), (flow, model)=>{
|
||||
return flow.then(()=>BrewData.create(model))
|
||||
}, Promise.resolve());
|
||||
})
|
||||
.then(() => {
|
||||
console.log(`Added ${BREW_COUNT} brews`);
|
||||
})
|
||||
.then(() => {
|
||||
return DB.close();
|
||||
})
|
||||
.catch(console.error);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user