Added missing inventory features

Smaller changes:
- Optimized a few SCSS files
This commit is contained in:
Lukas Obermann
2016-12-25 16:23:55 +13:00
parent b43f60f36f
commit 9413a6db1f
49 changed files with 5164 additions and 10773 deletions
+1568 -10356
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
+147
View File
@@ -0,0 +1,147 @@
# Material icon sprites
Sprite images allow you to use images more efficiently.
Our recommended way to use sprites is to create a single SVG file with the subset of icons needed for the project compiled as symbols.
This will keep the file small and allow for the greatest flexibility in referencing each icon.
It will also allow you to include any other svgs in the project, like a logo, in a single reference file.
Note that symbol files must be `<use>`ed or opened in an editor in order to be viewed, while sprite sheets can be opened and previewed directly.
## Creating your own sprites
While PNG and SVG icon images can be combined manually in an editor, using a processor to combine them automatically and generate any needed companion files is definitely the most convenient.
Here are three good options for creating your own CSS and SVG sprites.
* [Sprity](https://www.npmjs.com/package/sprity) (previously css-sprite) for PNG sprites with CSS sprite sheets.
* [svg-sprite](https://www.npmjs.com/package/svg-sprite) for SVG sprites with CSS sprite sheets.
* [svgstore](https://github.com/w0rm/gulp-svgstore) for SVG symbol sprites.
When creating a project, there are many similar and extended processors that can be for compilers such as gulp.
## Using the provided sprites
Material design icons come with CSS sprite sheets and SVG symbol sprites for each category of icon we include.
The icon sprites can be found in the `sprites` directory under `css-sprite` for png image sprites and `svg-sprite` for the various svg sprite techniques—including symbols.
Symbol sprites in the `svg-sprite` folder have an additional `-symbol` marker after their name.
If you are considering using svg sprites, you are encouraged to `<use>` the symbol sprites rather than traditional sprite sheets as they reduce the size of the files and the complexity and redundancy of using them.
## Using SVG symbol sprites
To add an icon using symbol sprites, you will need to add an svg element that `<use>`es the reference file with a link to the specific icon you want.
Since the icons are stored as `<symbol>`s, the viewbox is already set up and can be scaled relatively without needing to keep adjacent symbols in mind. You will only need set the size of the icon using CSS:
```CSS
.svg-24px {
width: 24px;
height: 24px;
}
```
Then reference the id for the icon you need:
```HTML
<svg class="svg-24px">
<use xlink:href="MaterialIcons.svg#ic_face_24px"></use>
</svg>
```
The HTML can also be simplified further by targeting all svg tags, and then overriding the size for individual classes and IDs.
While you will need to be careful not to let this hamper your layout, this kind of sizing is generally a matter of course.
To make 24px the default for `<svg>` tags, add the tag properties in css:
```CSS
svg {
width: 24px;
height: 24px;
}
```
Then reference the icon:
```HTML
<svg><use xlink:href="MaterialIcons.svg#ic_face_24px"></use></svg>
```
### Stacking symbol sprite icons
Using symbol sprites, icons can also easily be stacked on top of each other by including them in a single svg `<use>` statement.
Each icon can then be referenced using ids and classes and likewise handled using javascript.
E.g. To add a blue checkmark on top of a checkbox outline than can then be hidden when needed, first add the fill property in CSS:
```CSS
.svg-24px {
width: 24px;
height: 24px;
}
.check {
fill: blue;
}
```
Then add the check after the outline icon together with an id for the SVG tag (for targeting) and the check class for the checkmark:
```HTML
<svg class="svg-24px" id="checkbox-1">
<use xlink:href="MaterialIcons.svg#ic_check_box_outline_blank"></use>
<use class="check" xlink:href="MaterialIcons.svg#ic_check_box"></use>
</svg>
```
### External SVG symbols
The benefits to referencing an external svg come in the form of caching, as the same map file can be reused across the site and on subsequent visits.
Unfortunately, Internet Explorer, Edge, and older Android and iOS browsers cannot `<use>` external svg files.
Besides copying the svg file contents directly into each html file you need them in, there are two good ways to handle this:
1. Use the [svg4everybody polyfill](https://github.com/jonathantneal/svg4everybody) (this is the preferred method)
2. Use a simple AJAX call to include the symbol sprites in the document for all browsers, and then reference the svg directly wherever it is `<use>´d rather than the external file
### CSS Selectors, Transformation and the Shadow DOM
For some projects, parts of individual icons may be required to change or animate in some way relative to the icon, rather than as a whole. For example, an icon where the top part flies away or two parts are given separate colors. This is the power given to inline svg, as opposed to a simple image file.
Browser support for the `<use>` element, however, is still not good enough to properly allow the targeting of its shadow DOM. While not impossible, it can make selecting individual paths on a page very tricky.
In projects where individual paths need to be targeted, it is best to copy them inline or inject the paths into the html at build or load time, regardless of browser, to prevent issues that may arise due to the shadow DOM nature of the `<use>` element. There are two ways to handle this depending on the project:
1. The recommended way to do this is automatically using a build script or with a tool like [gulp-inject](https://github.com/klei/gulp-inject) that can reference individual icons, as that will keep the html files small and prevent having to make an href request.
2. If the project is designed to generate all pages dynamically using very few static elements, or none at all, the svg elements can instead be pulled in using javascript ajax calls. In this case, a purely concatenated xml file containing the individual icons required would work better and be easier to target than a symbol map.
## Using CSS sprite sheets
To use a CSS sprite sheet, reference the stylesheet for the icon category you wish to use, then include the icon definition in your markup.
E.g. using one of the play icons in `css-sprite-av`...
Reference the stylesheet:
```html
<link href="png/sprite-av-black.css" rel="stylesheet">
```
Create an element which will use the icon as a background, and include that icon as a class.
The example class here references the `icon` sprite sheet and specific `icon-ic_play_circle_outline_black_24dp` icon, which you can get from the above stylesheet.
```html
<div class="icon icon-ic_play_circle_outline_black_24dp"></div>
```
That's it! Well, for PNG sprites anyway.
If you are using svg sprites by referencing `svg/sprite-av-black.css` in this example instead, you will also need to set a dimension for the icon.
This can either be done inline or via a generic size class or a specific class such as this one:
```html
<style>
.svg-ic_play_circle_outline_black_24dp-dims { width: 24px; height: 24px; }
</style>
```
Then, make sure you set the dimension for the specific icon, `svg-ic_play_circle_outline_black_24dp`, which you can get from the svg stylesheet.
```html
<div class="svg-ic_play_circle_outline_black_24dp svg-ic_play_circle_outline_black_24dp-dims"></div>
```
Don't forget to publish the corresponding CSS and SVG/PNG files when deploying your project.
+36
View File
@@ -0,0 +1,36 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(MaterialIcons-Regular.woff2) format('woff2'),
url(MaterialIcons-Regular.woff) format('woff'),
url(MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
+1 -1
View File
@@ -1,5 +1,5 @@
@include font-face('C5AUI', 'fonts/Cha5App UI/Cha5App-UI', normal, normal, $file-formats: ttf);
@include font-face('Material', 'fonts/Material/Material', normal, normal, $file-formats: ttf);
@include font-face('Material', 'fonts/Material/MaterialIcons-Regular', normal, normal, $file-formats: ttf);
@include font-face('Alegreya', 'fonts/Alegreya-Bold', 700, normal, $file-formats: ttf);
@include font-face('Alegreya', 'fonts/Alegreya-BoldItalic', 700, italic, $file-formats: ttf);
+2
View File
@@ -15,6 +15,8 @@ $color6: #cdbe91;
$color7: #c89b3c;
$color8: #463714;
$color9: #1e282d;
$color10: #3c3732;
$color11: #31353a;
$link-color1: #0080b9;
$link-color2: #52abd1;
+18
View File
@@ -22,5 +22,23 @@ export default {
},
showItemEditor: function(item) {
createOverlay(<ItemEditor item={item} />);
},
addToList: function(item) {
AppDispatcher.dispatch({
actionType: ActionTypes.ADD_ITEM,
item
});
},
saveItem: function(item) {
AppDispatcher.dispatch({
actionType: ActionTypes.SAVE_ITEM,
item
});
},
removeFromList: function(id) {
AppDispatcher.dispatch({
actionType: ActionTypes.REMOVE_ITEM,
id
});
}
};
+5 -4
View File
@@ -1,6 +1,7 @@
import GeminiScrollbar from 'react-gemini-scrollbar';
import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';
import GeminiScrollbar from 'react-gemini-scrollbar';
import Label from './Label';
import React, { Component, PropTypes } from 'react';
export default class Dropdown extends Component {
@@ -86,7 +87,7 @@ export default class Dropdown extends Component {
const classNameInner = classNames( option[1] === this.props.value && 'active' );
return (
<div className={classNameInner} key={option[1]} onClick={this.onChange.bind(null, option[1])}>
<div className={classNameInner} key={option[1]} onClick={this.props.disabled ? null : this.onChange.bind(null, option[1])}>
{option[0]}
</div>
);
@@ -99,7 +100,7 @@ export default class Dropdown extends Component {
return (
<div className={className} ref="container">
{labelTextELement}
<Label text={this.props.label} disabled={this.props.disabled}></Label>
<div onMouseDown={this.insideFocus} onMouseUp={this.insideBlur} onTouchStart={this.insideFocus} onTouchEnd={this.insideBlur}>
{this.state.position === 'top' && this.state.isOpen ? downElement : <div style={{height:0}}></div>}
<div onClick={this.switch} className="value">{valueText}</div>
-2
View File
@@ -2,8 +2,6 @@
width: 280px;
margin: 10px 0;
font: 500 13px/30px Alegreya Sans;
@extend %label;
> div {
position: relative;
+6
View File
@@ -0,0 +1,6 @@
hr {
margin: 15px 0 5px;
border: none;
height: 1px;
background: $color9;
}
+5 -2
View File
@@ -1,17 +1,20 @@
import classNames from 'classnames';
import React, { Component, PropTypes } from 'react';
export default class Label extends Component {
static propTypes = {
className: PropTypes.string,
disabled: PropTypes.bool,
text: PropTypes.string
};
render() {
let { text } = this.props;
let { className, disabled, text, ...other } = this.props;
return text ? (
<label>{text}</label>
<label {...other} className={classNames(className, disabled && 'disabled')}>{text}</label>
) : null;
}
}
+4
View File
@@ -4,4 +4,8 @@ label {
display: block;
font: 500 12px/1 Alegreya Sans;
padding-bottom: 6px;
&.disabled {
color: $color11;
}
}
+22 -14
View File
@@ -3,13 +3,15 @@
border: 2px solid $color8;
background: $background2;
color: $color2;
box-shadow: 0 0 0 1px transparentiz(black, .6);
z-index: 2000;
> div {
padding: 21px 14px 18px;
padding: 18px;
}
h4 {
font: bold 14px/14px Alegreya SC;
font: bold 14px/18px Alegreya SC;
text-transform: uppercase;
color: $color1;
display: flex;
@@ -19,18 +21,15 @@
span:last-child {
color: $color2;
}
&:last-child {
margin-bottom: 3px;
}
}
p {
font: 500 12px/18px Alegreya Sans;
font: 500 12px/16px Alegreya Sans;
letter-spacing: 0.05em;
margin-top: 16px;
&:not(:nth-child(2)) {
margin-top: 0;
&:last-child {
margin-bottom: 1px;
}
}
@@ -46,14 +45,23 @@
transform: rotate(45deg);
@include calc(left, "50% - 8px");
bottom: -8px;
pointer-events: none;
}
}
> .calc-attr-overlay {
width: 280px;
p.calc-text {
font-style: italic;
&.overlay-bottom {
&:before {
content: "";
position: absolute;
@include size(12px);
border-width: 2px;
border-style: solid;
border-color: transparent $color8 $color8 transparent;
background: $background2;
transform: rotate(-135deg);
@include calc(left, "50% - 8px");
top: -8px;
pointer-events: none;
}
}
}
+59 -59
View File
@@ -115,79 +115,79 @@
}
}
.scroll-list {
table {
width: 100%;
border-collapse: collapse;
white-space: nowrap;
// .scroll-list {
// table {
// width: 100%;
// border-collapse: collapse;
// white-space: nowrap;
td {
padding: 5px 5px 5px 11px;
// td {
// padding: 5px 5px 5px 11px;
&.name {
width: 100%;
}
// &.name {
// width: 100%;
// }
&.name, &.fw {
color: #f0e6d2;
}
}
// &.name, &.fw {
// color: #f0e6d2;
// }
// }
thead {
font: bold 13px/22px Alegreya SC;
// thead {
// font: bold 13px/22px Alegreya SC;
tr td {
border-bottom: 1px solid transparentize(white, .925);
}
}
// tr td {
// border-bottom: 1px solid transparentize(white, .925);
// }
// }
tbody {
tr {
&:not(:last-child) td {
height: 36px;
border-bottom: 1px solid transparentize(white, .925);
}
// tbody {
// tr {
// &:not(:last-child) td {
// height: 36px;
// border-bottom: 1px solid transparentize(white, .925);
// }
&:hover {
background: transparentize(white, .975);
}
// &:hover {
// background: transparentize(white, .975);
// }
td {
height: 36px;
// td {
// height: 36px;
&.name {
vertical-align: middle;
// &.name {
// vertical-align: middle;
h2 {
font: 500 15px/15px Alegreya Sans;
text-transform: none;
color: #f0e6d2;
display: inline-block;
}
}
// h2 {
// font: 500 15px/15px Alegreya Sans;
// text-transform: none;
// color: #f0e6d2;
// display: inline-block;
// }
// }
&.check, &.skt {
font: 13px/13px Alegreya SC;
}
// &.check, &.skt {
// font: 13px/13px Alegreya SC;
// }
&.inc {
font-weight: 500;
// &.inc {
// font-weight: 500;
.btn {
padding: 0 14px 3px;
min-width: auto;
font-size: 16px;
}
}
// .btn {
// padding: 0 14px 3px;
// min-width: auto;
// font-size: 16px;
// }
// }
.textfield, .dropdown {
width: 200px;
}
}
}
}
}
}
// .textfield, .dropdown {
// width: 200px;
// }
// }
// }
// }
// }
// }
h4 {
font-size: 13px;
+32
View File
@@ -0,0 +1,32 @@
import RadioButtonGroup from './RadioButtonGroup';
import React, { Component, PropTypes } from 'react';
const SORT_NAMES = {
name: 'Alphabetisch',
group: 'Nach Gruppe',
groupname: 'Nach Gruppe',
where: 'Nach Trageort'
};
export default class SortOptions extends Component {
static propTypes = {
options: PropTypes.array.isRequired,
sort: PropTypes.func.isRequired,
sortOrder: PropTypes.string.isRequired
}
render() {
const { options, sort, sortOrder, ...other } = this.props;
return (
<RadioButtonGroup
{...other}
active={sortOrder}
onClick={sort}
array={options.map(e => ({ name: SORT_NAMES[e], value: e }))}
/>
);
}
}
+50 -37
View File
@@ -6,7 +6,9 @@ table {
table-layout: auto;
width: 100%;
white-space: nowrap;
}
table.list {
td {
padding: 5px 5px 5px 11px;
@@ -22,7 +24,7 @@ table {
thead {
font: bold 13px/22px Alegreya SC;
tr td {
td {
border-bottom: 1px solid transparentize(white, .925);
}
}
@@ -30,58 +32,69 @@ table {
tbody {
tr {
&:not(:last-child) td {
height: 36px;
border-bottom: 1px solid transparentize(white, .925);
}
&:hover {
background: transparentize(white, .975);
}
}
td {
height: 36px;
&.name h2, &.fw {
font: 500 15px/15px Alegreya Sans;
text-transform: none;
color: #f0e6d2;
}
td {
height: 36px;
&.name h2, &.fw {
font: 500 15px/15px Alegreya Sans;
text-transform: none;
color: #f0e6d2;
}
&.check, &.skt {
font: 13px/13px Alegreya SC;
}
&.check, &.skt {
font: 13px/13px Alegreya SC;
}
&.inc {
font-weight: 500;
display: flex;
justify-content: flex-end;
align-items: center;
&.inc {
font-weight: 500;
display: flex;
justify-content: flex-end;
align-items: center;
.btn {
height: 26px;
padding: 0 14px 3px;
min-width: auto;
font-size: 16px;
flex: none;
.btn {
height: 26px;
padding: 0 14px 3px;
min-width: auto;
font-size: 16px;
flex: none;
&:nth-child(2) {
margin-left: 4px;
}
&:nth-child(2) {
margin-left: 4px;
}
}
}
}
}
// &.min {
// td {
// color: transparentize(#878683, .4);
&.large-list tbody td {
height: 42px;
// &.name h2, &.fw {
// font: 500 15px/15px Alegreya Sans;
// text-transform: none;
// color: transparentize(#f0e6d2, .4);
// }
// }
// }
&.name {
> div {
display: flex;
align-items: center;
> h2, > div {
flex: none;
margin: 0;
}
> div {
margin-left: 14px;
&.tiers {
width: 65px;
}
}
}
}
}
}
+3 -2
View File
@@ -56,8 +56,9 @@ export default class TextField extends Component {
<input
type={type}
value={value}
onChange={onChange}
onKeyPress={onKeyDown}
onChange={disabled ? null : onChange}
onKeyPress={disabled ? null : onKeyDown}
readOnly={disabled}
ref='inputElement'
/>
);
-2
View File
@@ -17,8 +17,6 @@
visibility: hidden;
}
}
@extend %label;
input, textarea {
width: 100%;
+28 -10
View File
@@ -2,12 +2,18 @@ import classNames from 'classnames';
import createOverlay, { close } from '../utils/createOverlay';
import Overlay from './Overlay';
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
export default class Tooltip extends Component {
static propTypes = {
content: PropTypes.node,
margin: PropTypes.number
margin: PropTypes.number,
position: PropTypes.string
};
static defaultProps = {
position: 'top'
};
state = {
@@ -17,26 +23,38 @@ export default class Tooltip extends Component {
triggerRef;
node;
componentDidMount() {
this.triggerRef = this.refs.trigger;
componentWillUnmount() {
if (this.node) {
close(this.node);
}
}
open = () => {
const { content, margin } = this.props;
this.node = createOverlay(<Overlay className="tooltip" position="top" trigger={this.triggerRef} margin={margin}>
const { content, margin, position } = this.props;
this.node = createOverlay(<Overlay className="tooltip" position={position} trigger={this.triggerRef} margin={margin}>
{content}
</Overlay>);
};
close = () => close(this.node);
close = () => {
close(this.node);
this.node = undefined;
};
render() {
const { children } = this.props;
const only = React.cloneElement(React.Children.only(children), {
onMouseEnter: this.open,
onMouseLeave: this.close,
ref: 'trigger'
onMouseOver: this.open,
onMouseOut: this.close,
ref: (node) => {
if (node !== null && node.nodeType !== 1) {
this.triggerRef = ReactDOM.findDOMNode(node);
}
else {
this.triggerRef = node;
}
}
});
return only;
+1
View File
@@ -16,6 +16,7 @@
@import "Checkbox";
@import "Dialog";
@import "Dropdown";
@import "Hr";
@import "IconButton";
@import "Label";
@import "LabelBox";
+34 -17
View File
@@ -18,6 +18,7 @@ import TitleBarLeft from './TitleBarLeft';
import TitleBarRight from './TitleBarRight';
import TitleBarTabs from './TitleBarTabs';
import TitleBarWrapper from './TitleBarWrapper';
import TooltipToggle from '../TooltipToggle';
export default class TitleBar extends Component {
@@ -72,7 +73,7 @@ export default class TitleBar extends Component {
render() {
const { currentSection, currentTab } = this.props;
const { account, ap: { total, spent }, avatar, isUndoAvailable, phase } = this.state;
const { account, ap: { total, spent, adv, disadv }, avatar, isUndoAvailable, phase } = this.state;
switch (currentSection) {
case 'main': {
@@ -97,6 +98,10 @@ export default class TitleBar extends Component {
primary
disabled
/>
<IconButton
icon="&#xE868;"
disabled
/>
</TitleBarRight>
</TitleBarWrapper>
);
@@ -115,6 +120,10 @@ export default class TitleBar extends Component {
{ label: account.name, tag: 'account' }
]} />
<BorderButton label="Abmelden" onClick={this.logout} disabled />
<IconButton
icon="&#xE868;"
disabled
/>
</TitleBarRight>
</TitleBarWrapper>
);
@@ -153,7 +162,30 @@ export default class TitleBar extends Component {
<TitleBarTabs active={currentTab} tabs={tabs} />
</TitleBarLeft>
<TitleBarRight>
<Text className="collected-ap">{total - spent} AP</Text>
<TooltipToggle
position="bottom"
margin={12}
content={
<div className="ap-details">
<h4>Abenteuerpunkte</h4>
<p className="general">
{total} AP gesamt<br/>
{spent} AP verwendet
</p>
<hr />
<p>
{adv[0]} / 80 AP für Vorteile<br/>
{adv[1] > 0 ? `${adv[1]} / 50 für magische Vorteile` : null}
{adv[2] > 0 ? `${adv[2]} / 50 für karmale Vorteile` : null}
{disadv[0]} / 80 AP für Nachteile<br/>
{disadv[1] > 0 ? `${disadv[1]} / 50 für magische Nachteile` : null}
{disadv[2] > 0 ? `${disadv[2]} / 50 für karmale Nachteile` : null}
</p>
</div>
}
>
<Text className="collected-ap">{total - spent} AP</Text>
</TooltipToggle>
<IconButton
icon="&#xE166;"
onClick={this.undo}
@@ -187,18 +219,3 @@ export default class TitleBar extends Component {
}
}
}
// <div className="details">
// <div className="all"><span>{this.state.ap}</span> AP gesamt</div>
// <div className="used"><span>{this.state.used}</span> AP verwendet</div>
// <hr />
// <div className="adv">
// <span>{this.state.disadv.adv[0]} / 80</span> AP für Vorteile
// {this.state.disadv.adv[1] > 0 ? ` (davon ${this.state.disadv.adv[1]} für magische)`:null}
// {this.state.disadv.adv[2] > 0 ? ` (davon ${this.state.disadv.adv[2]} für karmale)`:null}
// </div>
// <div className="disadv">
// <span>{this.state.disadv.disadv[0]} / 80</span> AP für Nachteile
// {this.state.disadv.disadv[1] > 0 ? `(davon ${this.state.disadv.disadv[1]} für magische)`:null}
// {this.state.disadv.disadv[2] > 0 ? `(davon ${this.state.disadv.disadv[2]} für karmale)`:null}
// </div>
// </div>
+1 -1
View File
@@ -8,7 +8,7 @@
padding-left: 4px;
+ div {
margin-left: 20px;
margin-left: 28px;
}
&:after {
+5 -69
View File
@@ -22,81 +22,17 @@
.btn {
margin-left: 11px;
flex: none;
}
.collected-ap {
color: #a19b8f;
flex: 1 0 auto;
font: bold 15px/#{$titlebar-height} Alegreya SC;
font: bold 15px/32px Alegreya SC;
height: 32px;
letter-spacing: 0.1em;
margin: 0 11px 0 35px;
text-transform: uppercase;
color: #a19b8f;
position: relative;
}
// .ap:hover + .details {
// visibility: visible;
// }
// .details {
// background: #010a13;
// border: 1px solid #785a28;
// position: absolute;
// font: 500 12px/18px Alegreya Sans;
// letter-spacing: 0.05em;
// color: #878683;
// padding: 13px 0;
// top: $titlebar-height - 12px;
// // right: 112px;
// white-space: nowrap;
// // width: 2px;
// visibility: hidden;
// // transition: all 0.2s cubic-bezier(0.165, 0.84, 0.44, 1);
// // min-width: 150px;
// right: 16px;
// padding: 13px 18px;
// box-shadow: 0 0 18px 4px black;
// &:before, &:after {
// position: absolute;
// display: block;
// content: "";
// @include calc(width, '100% - 10px');
// height: 2px;
// left: 4px;
// background: #785a28;
// }
// &:before {
// top: -5px;
// }
// &:after {
// bottom: -5px;
// }
// div {
// overflow: hidden;
// }
// hr {
// margin: 10px 0;
// padding: 0;
// background: #1f282d;
// height: 1px;
// }
// span {
// color: #f1e6d4;
// }
// }
.menu-button {
flex: 1 0 auto;
font: 24px/#{$titlebar-height} Material;
width: $titlebar-height;
text-align: center;
display: none;
color: #f1e6d4;
text-transform: uppercase;
}
}
+3
View File
@@ -143,6 +143,9 @@ export default keyMirror({
// InventoryStore
FILTER_ITEMS: null,
SORT_ITEMS: null,
ADD_ITEM: null,
SAVE_ITEM: null,
REMOVE_ITEM: null,
// GroupsStore
SHOW_MASTER_REQUESTED_LIST: null,
+1 -1
View File
@@ -8,7 +8,7 @@ var _heroes = {
date: new Date('2016-10-18T16:18:28.420Z'),
player: ['U_1', 'schuchi'],
id: 'H_1',
phase: 2,
phase: 3,
name: 'Shimo ibn Rashdul',
avatar: 'images/portrait.png',
ap: {
+160 -2
View File
@@ -1,12 +1,99 @@
import AppDispatcher from '../dispatcher/AppDispatcher';
import Store from './Store';
import ActionTypes from '../constants/ActionTypes';
import { Item } from '../utils/DataUtils';
var _itemsById = {};
var _items = [];
var _itemsById = {
ITEM_1: new Item({
id: 'ITEM_1',
name: 'Mörderstorchsäbel',
price: 100,
weight: 1.2,
number: 1,
where: 'Gürtel',
gr: 1,
combattechnique: 'CT_12',
damageDiceNumber: 1,
damageDiceSides: 6,
damageFlat: 6,
damageBonus: 14,
at: -1,
pa: 0,
reach: 2,
length: 76,
stp: 0,
range: [0, 0, 0],
reloadtime: 0,
ammunition: null,
pro: 0,
enc: 0,
addpenalties: false
}),
ITEM_2: new Item({
id: 'ITEM_2',
name: 'Bogen des Rakorium Muntagonus',
price: 1234,
weight: 0.9,
number: 1,
where: 'Fliegt',
gr: 2,
combattechnique: 'CT_2',
damageDiceNumber: 2,
damageDiceSides: 6,
damageFlat: 0,
damageBonus: 0,
at: 0,
pa: 0,
reach: 0,
length: 123,
stp: 0,
range: [90, 60, 90],
reloadtime: 1,
ammunition: null,
pro: 0,
enc: 0,
addpenalties: false
}),
ITEM_3: new Item({
id: 'ITEM_3',
name: 'Rüstung des Widderhorns',
price: 30,
weight: 4,
number: 1,
where: '',
gr: 3,
combattechnique: '',
damageDiceNumber: 0,
damageDiceSides: 6,
damageFlat: 0,
damageBonus: 0,
at: 0,
pa: 0,
reach: 0,
length: 123,
stp: 0,
range: [90, 60, 90],
reloadtime: 1,
ammunition: null,
pro: 4,
enc: 2,
addpenalties: true
})
};
var _items = ['ITEM_1','ITEM_2','ITEM_3'];
var _itemTemplatesById = {};
var _itemTemplates = [];
var _filterText = '';
var _sortOrder = 'name';
function _init(raw) {
for (const id in raw) {
_itemTemplatesById[id] = new Item({ ...raw[id], isTemplateLocked: true });
_itemTemplates.push(id);
}
console.log(_itemTemplatesById[_itemTemplates[0]]);
}
function _updateFilterText(text) {
_filterText = text;
}
@@ -15,6 +102,49 @@ function _updateSortOrder(option) {
_sortOrder = option;
}
function _addItem(raw, id) {
// if ([1,2].includes(data.gr)) {
// data.ddn = parseInt(data.ddn) || 0;
// data.df = parseInt(data.df) || 0;
// data.length = parseInt(data.length) || 0;
// }
// if (data.gr === 1) {
// data.db = parseInt(data.db) || 0;
// data.at = parseInt(data.at) || 0;
// data.pa = parseInt(data.pa) || 0;
// data.reach = parseInt(data.reach) || 0;
// data.stp = parseInt(data.stp) || 0;
// }
// else if (data.gr === 2) {
// data.rb1 = parseInt(data.rb1) || 0;
// data.rb2 = parseInt(data.rb2) || 0;
// data.rb3 = parseInt(data.rb3) || 0;
// data.range = [ data.rb1, data.rb2, data.rb3 ];
// data.rt = parseInt(data.rt) || 0;
// }
// else if (data.gr === 3) {
// data.pro = parseInt(data.pro) || 0;
// data.enc = parseInt(data.enc) || 0;
// }
_itemsById[id] = new Item({ ...Item.prepareDataForStore(raw), id });
_items.push(id);
}
function _saveItem(raw) {
_itemsById[raw.id] = new Item(Item.prepareDataForStore(raw));
}
function _removeItem(id) {
delete _itemsById[id];
_items.some((e,i) => {
if (e === id) {
_items.splice(i, 1);
return true;
}
return false;
});
}
class _InventoryStore extends Store {
get(id) {
@@ -25,6 +155,14 @@ class _InventoryStore extends Store {
return _items.map(e => _itemsById[e]);
}
getTemplate(id) {
return _itemTemplatesById[id];
}
getAllTemplates() {
return _itemTemplates.map(e => _itemTemplatesById[e]);
}
getFilterText() {
return _filterText;
}
@@ -33,6 +171,10 @@ class _InventoryStore extends Store {
return _sortOrder;
}
getForEditor(id) {
return Item.prepareDataForEditor(_itemsById[id]);
}
}
const InventoryStore = new _InventoryStore();
@@ -49,6 +191,22 @@ InventoryStore.dispatchToken = AppDispatcher.register(payload => {
_updateSortOrder(payload.option);
break;
case ActionTypes.ADD_ITEM:
_addItem(payload.item, 'ITEM_' + (_items[_items.length - 1] ? _items[_items.length - 1].split('_')[1] + 1 : 1));
break;
case ActionTypes.SAVE_ITEM:
_saveItem(payload.item);
break;
case ActionTypes.REMOVE_ITEM:
_removeItem(payload.id);
break;
case ActionTypes.RECEIVE_RAW_LISTS:
_init(payload.items);
break;
default:
return true;
}
+22 -3
View File
@@ -24,6 +24,14 @@ export const sortByCostSex = (a,b) => a.ap < b.ap ? -1 : a.ap > b.ap ? 1 : sortB
export const sortByGroup = (a,b) => a.gr < b.gr ? -1 : a.gr > b.gr ? 1 : sortByName(a,b);
var GROUPS;
export const sortByGroupName = (a,b) => {
let agr = GROUPS[a.gr - 1];
let bgr = GROUPS[b.gr - 1];
return agr < bgr ? -1 : agr > bgr ? 1 : sortByName(a,b);
};
export const sortByIC = (a,b) => a.ic < b.ic ? -1 : a.ic > b.ic ? 1 : sortByName(a,b);
export const sortByProperty = (a,b) => {
@@ -41,6 +49,8 @@ export const sortByPrice = (a,b) => a.price < b.price ? -1 : a.price > b.price ?
export const sortByWeight = (a,b) => a.weight < b.weight ? -1 : a.weight > b.weight ? 1 : sortByName(a,b);
export const sortByWhere = (a,b) => a.where < b.where ? -1 : a.where > b.where ? 1 : sortByName(a,b);
export const sort = (list, sortOrder) => {
let sort;
switch (sortOrder) {
@@ -53,6 +63,9 @@ export const sort = (list, sortOrder) => {
case 'group':
sort = sortByGroup;
break;
case 'groupname':
sort = sortByGroupName;
break;
case 'ic':
sort = sortByIC;
break;
@@ -68,6 +81,9 @@ export const sort = (list, sortOrder) => {
case 'weight':
sort = sortByWeight;
break;
case 'where':
sort = sortByWhere;
break;
default:
return list;
@@ -92,9 +108,12 @@ export const sortSex = (list, sortOrder, sex) => {
return list.sort(sort);
};
export const filterAndSort = (list, filterText, sortOrder, sex) => {
if (sex) {
return sortSex(filter(list, filterText), sortOrder, sex);
export const filterAndSort = (list, filterText, sortOrder, option) => {
if (Array.isArray(option)) {
GROUPS = option;
}
else if (option) {
return sortSex(filter(list, filterText), sortOrder, option);
}
return sort(filter(list, filterText), sortOrder);
};
+55 -41
View File
@@ -1,6 +1,5 @@
import AuthStore from '../stores/AuthStore';
import ProfileStore from '../stores/ProfileStore';
import { get, post } from './request';
import ServerActions from '../actions/ServerActions';
export default {
@@ -10,7 +9,8 @@ export default {
getAllData: async function() {
try {
let result = await get('data/DSA5.json', 'json');
let response = await fetch('data/DSA5.json');
let result = await response.json();
ServerActions.receiveLists(result);
} catch(e) {
ServerActions.connectionError(e);
@@ -19,7 +19,8 @@ export default {
register: async function(email, username, displayname, password) {
try {
ServerActions.startLoading();
let result = await get('data/register.php?email=' + email + '&name=' + username + '&display=' + displayname + '&password=' + password);
let response = await fetch('data/register.php?email=' + email + '&name=' + username + '&display=' + displayname + '&password=' + password);
let result = await response.text();
ServerActions.registrationSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
@@ -27,7 +28,8 @@ export default {
},
checkEmail: async function(email) {
try {
let result = await get('data/checkemail.php?e=' + email);
let response = await fetch('data/checkemail.php?e=' + email);
let result = await response.text();
return result;
} catch(e) {
ServerActions.connectionError(e);
@@ -35,7 +37,8 @@ export default {
},
checkUsername: async function(username) {
try {
let result = await get('data/checkuser.php?e=' + username);
let response = await fetch('data/checkuser.php?e=' + username);
let result = await response.text();
return result;
} catch(e) {
ServerActions.connectionError(e);
@@ -44,7 +47,8 @@ export default {
sendPasswordCode: async function(email) {
try {
ServerActions.startLoading();
let result = await get('php/forgetpw.php?e=' + email);
let response = await fetch('php/forgetpw.php?e=' + email);
let result = await response.text();
ServerActions.forgotPasswordSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
@@ -53,7 +57,8 @@ export default {
sendUsername: async function(email) {
try {
ServerActions.startLoading();
let result = await get('php/forgetusername.php?e=' + email);
let response = await fetch('php/forgetusername.php?e=' + email);
let result = await response.text();
ServerActions.forgotUsernameSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
@@ -62,7 +67,8 @@ export default {
resendActivation: async function(email) {
try {
ServerActions.startLoading();
let result = await get('php/regmailagain.php?e=' + email);
let response = await fetch('php/regmailagain.php?e=' + email);
let result = await response.text();
ServerActions.resendActivationSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
@@ -71,7 +77,8 @@ export default {
login: async function(username, password) {
try {
ServerActions.startLoading();
let result = await get('php/login.php?u=' + username + '&p=' + password);
let response = await fetch('php/login.php?u=' + username + '&p=' + password);
let result = await response.text();
ServerActions.receiveAccount(result, username);
} catch(e) {
ServerActions.connectionError(e);
@@ -83,7 +90,8 @@ export default {
// logout: async function() {
// try {
// ServerActions.startLoading();
// let result = await get('php/logout.php?uid=' + AuthStore.getID());
// let response = await fetch('php/logout.php?uid=' + AuthStore.getID());
// let result = await response.text();
// ServerActions.logoutSuccess(result);
// } catch(e) {
// ServerActions.connectionError(e);
@@ -92,9 +100,8 @@ export default {
setNewUsername: async function(name) {
try {
ServerActions.startLoading();
let userID = AuthStore.getID();
let url = 'php/changeaccount.php?uid=' + userID + '&src=username&v=' + name;
let result = await get(url);
let response = await fetch('php/changeaccount.php?uid=' + AuthStore.getID() + '&src=username&v=' + name);
let result = await response.text();
ServerActions.changeUsernameSuccess(result, name);
} catch(e) {
ServerActions.connectionError(e);
@@ -103,9 +110,8 @@ export default {
setNewPassword: async function(password) {
try {
ServerActions.startLoading();
let userID = AuthStore.getID();
let url = 'php/changeaccount.php?uid=' + userID + '&src=password&v=' + password;
let result = await get(url);
let response = await fetch('php/changeaccount.php?uid=' + AuthStore.getID() + '&src=password&v=' + password);
let result = await response.text();
ServerActions.changePasswordSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
@@ -114,7 +120,8 @@ export default {
deleteAccount: async function() {
try {
ServerActions.startLoading();
let result = await get('php/deleteaccount.php?uid=' + AuthStore.getID());
let response = await fetch('php/deleteaccount.php?uid=' + AuthStore.getID());
let result = await response.text();
ServerActions.deleteAccountSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
@@ -123,7 +130,8 @@ export default {
getHeroes: async function() {
try {
ServerActions.startLoading();
let result = await get('php/getherolist.php?uid=' + AuthStore.getID());
let response = await fetch('php/getherolist.php?uid=' + AuthStore.getID());
let result = await response.text();
ServerActions.herolistRefreshSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
@@ -193,7 +201,8 @@ export default {
// loadHero: async function(id) {
// try {
// ServerActions.startLoading();
// let result = await get('php/gethero.php?hid=' + id);
// let response = await fetch('php/gethero.php?hid=' + id);
// let result = await response.text();
// ServerActions.loadHeroSuccess(id, result);
// } catch(e) {
// ServerActions.connectionError(e);
@@ -202,35 +211,40 @@ export default {
createNewHero: async function(heroname) {
try {
ServerActions.startLoading();
let url = 'php/newhero.php?uid=' + AuthStore.getID() + '&n=' + heroname;
let result = await get(url);
let response = await fetch('php/newhero.php?uid=' + AuthStore.getID() + '&n=' + heroname);
let result = await response.text();
ServerActions.createNewHeroSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
}
},
saveHero: function(data) {
ServerActions.startLoading();
var blob = new Blob([data], { type: "application/json" });
var url = window.URL.createObjectURL(blob);
window.open(url);
},
// saveHero: async function(data) {
// try {
// ServerActions.startLoading();
// let url = 'php/save.php?short=' + data[0] + '&full=' + data[1];
// let result = await get(url);
// ServerActions.saveHeroSuccess(result);
// } catch(e) {
// ServerActions.connectionError(e);
// }
// saveHero: function(data) {
// ServerActions.startLoading();
// var blob = new Blob([data], { type: "application/json" });
// var url = window.URL.createObjectURL(blob);
// window.open(url);
// },
saveHero: async function(data) {
try {
ServerActions.startLoading();
let response = await fetch('php/save.php?short=' + data[0] + '&full=' + data[1], {
method: 'post',
body: JSON.stringify(data)
});
let result = await response.text();
ServerActions.saveHeroSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
}
},
changeHeroAvatar: async function(type, data) {
try {
ServerActions.startLoading();
var finalData = new FormData(data);
let url = 'php/uploadheropic.php?hid=' + ProfileStore.getID();
let result = await post(url, finalData);
let response = await fetch('php/uploadheropic.php?hid=' + ProfileStore.getID(), {
method: 'post',
body: new FormData(data)
});
let result = await response.text();
ServerActions.changeHeroAvatarSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
@@ -239,8 +253,8 @@ export default {
deleteHero: async function(heroid) {
try {
ServerActions.startLoading();
let url = 'php/deletehero.php?uid=' + AuthStore.getID() + '&hid=' + heroid;
let result = await get(url);
let response = await fetch('php/deletehero.php?uid=' + AuthStore.getID() + '&hid=' + heroid);
let result = await response.text();
ServerActions.deleteHeroSuccess(result);
} catch(e) {
ServerActions.connectionError(e);
+79 -21
View File
@@ -7,48 +7,106 @@ export default class Item extends Core {
let {
price,
weight,
number,
where,
gr,
ct,
ddn,
dds,
df,
db,
combattechnique,
damageDiceNumber,
damageDiceSides,
damageFlat,
damageBonus,
at,
pa,
re,
reach,
length,
stp,
range,
rt,
am,
reloadtime,
ammunition,
pro,
enc,
addp
addpenalties,
template,
isTemplateLocked
} = args;
this.price = price;
this.weight = weight;
this.number = number;
this.gr = gr;
this.combattechnique = ct;
this.damageDiceNumber = ddn;
this.damageDiceSides = dds;
this.damageFlat = df;
this.damageBonus = db;
this.combattechnique = combattechnique;
this.damageDiceNumber = damageDiceNumber;
this.damageDiceSides = damageDiceSides;
this.damageFlat = damageFlat;
this.damageBonus = damageBonus;
this.at = at;
this.pa = pa;
this.reach = re;
this.reach = reach;
this.length = length;
this.stp = stp;
this.range = range;
this.reloadtime = rt;
this.ammunition = am;
this.reloadtime = reloadtime;
this.ammunition = ammunition;
this.pro = pro;
this.enc = enc;
this.addpenalties = addp;
this.addpenalties = addpenalties;
this.number = 1;
this.where = '';
this.template = 'ITEMTPL_0';
this.where = where;
this.template = template;
this.isTemplateLocked = isTemplateLocked || false;
}
static prepareDataForStore(target) {
target.range = [];
for (const name in target) {
const value = target[name];
switch (name) {
case 'price':
case 'weight':
target[name] = value ? (typeof value === 'number' ? value : parseInt(value.replace(',','.'))) : value;
break;
case 'number':
target[name] = value ? (typeof value === 'number' ? value : parseInt(value)) : value;
break;
case 'damageDiceNumber':
case 'damageFlat':
case 'damageBonus':
case 'length':
case 'at':
case 'pa':
case 'stp':
case 'reloadtime':
case 'pro':
case 'enc':
target[name] = value ? parseInt(value) : value;
break;
case 'range1':
target.range[0] = value;
break;
case 'range2':
target.range[1] = value;
break;
case 'range3':
target.range[2] = value;
break;
default:
target[name] = value;
}
}
return target;
}
static prepareDataForEditor(target) {
target.range1 = target.range[0];
target.range2 = target.range[1];
target.range3 = target.range[2];
return target;
}
}
+9 -1
View File
@@ -4,7 +4,7 @@
@include calc(width, "100% - 70px");
height: 1px;
content: "";
background: #3c3732;
background: $color10;
// background: #3a322b;
// background: #1f282d;
top: 0;
@@ -55,6 +55,14 @@
}
}
@at-root .overlay > .calc-attr-overlay {
width: 280px;
p.calc-text {
font-style: italic;
}
}
> .btn-round {
margin-top: 5px;
}
+4 -2
View File
@@ -33,8 +33,10 @@ export default class AttributeCalcItem extends Component {
<AttributeBorder label={short} value={value} tooltip={<div className="calc-attr-overlay">
<h4><span>{label}</span><span>{value}</span></h4>
<p className="calc-text">{calc} = {value || '-'}</p>
{ mod || mod === 0 ? <p className="mod">Modifikator: {mod}</p> : null}
{ (currentAdd || currentAdd === 0) && phase > 2 ? <p className="add">Gekauft: {currentAdd} / {maxAdd || '-'}</p> : null}
{ mod || mod === 0 || ((currentAdd || currentAdd === 0) && phase > 2) ? <p>
{ mod || mod === 0 ? <span className="mod">Modifikator: {mod}<br/></span> : null}
{ (currentAdd || currentAdd === 0) && phase > 2 ? <span className="add">Gekauft: {currentAdd} / {maxAdd || '-'}</span> : null}
</p> : null}
</div>} tooltipMargin={7}>
{ phase > 2 && maxAdd ? <NumberBox current={currentAdd} max={maxAdd} /> : null }
{increaseElement}
+1 -1
View File
@@ -20,7 +20,7 @@ export default class DisAdvList extends Component {
return (
<Scroll className="list">
<table>
<table className="list large-list">
<thead>
<tr>
<td className="name">{type === 'ADV' ? 'Vorteil' : 'Nachteil'}</td>
+1 -1
View File
@@ -4,7 +4,7 @@
@include calc(width, "100% - 70px");
height: 1px;
content: "";
background: #3c3732;
background: $color10;
// background: #3a322b;
// background: #1f282d;
top: $titlebar-height;
+42 -15
View File
@@ -1,12 +1,16 @@
import { filterAndSort } from '../../utils/ListUtils';
import BorderButton from '../../components/BorderButton';
import InventoryActions from '../../actions/InventoryActions';
import InventoryListItem from './InventoryListItem';
import InventoryStore from '../../stores/InventoryStore';
import RadioButtonGroup from '../../components/RadioButtonGroup';
import SortOptions from '../../components/SortOptions';
import React, { Component } from 'react';
import Scroll from '../../components/Scroll';
import Slidein from '../../components/Slidein';
import TextField from '../../components/TextField';
const GROUPS = ['Nahkampfwaffen', 'Fernkampfwaffen', 'Rüstungen', 'Munition', 'Waffenzubehör', 'Kleidung', 'Reisebedarf und Werkzeuge', 'Beleuchtung', 'Verbandzeug und Heilmittel', 'Behältnisse', 'Seile und Ketten', 'Diebeswerkzeug', 'Handwerkszeug', 'Orientierungshilfen', 'Schmuck', 'Edelsteine und Feingestein', 'Schreibwaren', 'Bücher', 'Magische Artefakte', 'Alchimica', 'Gifte', 'Heilkräuter', 'Musikinstrumente', 'Genussmittel und Luxus', 'Tiere', 'Tierbedarf', 'Forbewegungsmittel'];
const getInventoryStore = () => ({
items: InventoryStore.getAll(),
filterText: InventoryStore.getFilterText(),
@@ -15,7 +19,10 @@ const getInventoryStore = () => ({
export default class Inventory extends Component {
state = getInventoryStore();
state = {
...getInventoryStore(),
templates: InventoryStore.getAllTemplates()
};
_updateInventoryStore = () => this.setState(getInventoryStore());
@@ -31,30 +38,50 @@ export default class Inventory extends Component {
}
showItemCreation = () => InventoryActions.showItemCreation();
showAddSlidein = () => this.setState({ showAddSlidein: true });
hideAddSlidein = () => this.setState({ showAddSlidein: false });
render() {
const { filterText, items, sortOrder } = this.state;
const { filterText, items, showAddSlidein, sortOrder, templates } = this.state;
const list = filterAndSort(items, filterText, sortOrder);
const list = filterAndSort(items, filterText, sortOrder, GROUPS);
const templateList = filterAndSort(templates, filterText, 'name');
return (
<div className="page" id="inventory">
<Slidein isOpen={showAddSlidein} close={this.hideAddSlidein}>
<div className="options">
<TextField hint="Suchen" value={filterText} onChange={this.filter} fullWidth />
</div>
<Scroll className="list">
<table className="list large-list">
<thead>
<tr>
<td className="name">Gegenstand</td>
<td className="inc"></td>
</tr>
</thead>
<tbody>
{
templateList.map(obj => <InventoryListItem key={obj.id} data={obj} add />)
}
</tbody>
</table>
</Scroll>
</Slidein>
<div className="options">
<TextField hint="Suchen" value={filterText} onChange={this.filter} fullWidth />
<RadioButtonGroup
active={sortOrder}
onClick={this.sort}
array={[
{ name: 'Alphabetisch', value: 'name' },
{ name: 'Gruppen', value: 'group' }
]}
<SortOptions
options={[ 'name', 'groupname', 'where' ]}
sortOrder={sortOrder}
sort={this.sort}
/>
<BorderButton label="Hinzufügen" disabled />
<BorderButton label="Erstellen" onClick={this.showItemCreation} />
<BorderButton label="Hinzufügen" onClick={this.showAddSlidein} />
<BorderButton label="Erstellen" onClick={this.showItemCreation} />
</div>
<Scroll className="list">
<table>
<table className="list large-list">
<thead>
<tr>
<td className="type">Gruppe</td>
@@ -68,7 +95,7 @@ export default class Inventory extends Component {
</thead>
<tbody>
{
list
list.map(obj => <InventoryListItem key={obj.id} data={obj} />)
}
</tbody>
</table>
+175
View File
@@ -0,0 +1,175 @@
import IconButton from '../../components/IconButton';
import { Item } from '../../utils/DataUtils';
import { get } from '../../stores/ListStore';
import InventoryActions from '../../actions/InventoryActions';
import InventoryStore from '../../stores/InventoryStore';
import React, { Component, PropTypes } from 'react';
import TooltipToggle from '../../components/TooltipToggle';
const GROUPS = ['Nahkampfwaffen', 'Fernkampfwaffen', 'Rüstungen', 'Munition', 'Waffenzubehör', 'Kleidung', 'Reisebedarf und Werkzeuge', 'Beleuchtung', 'Verbandzeug und Heilmittel', 'Behältnisse', 'Seile und Ketten', 'Diebeswerkzeug', 'Handwerkszeug', 'Orientierungshilfen', 'Schmuck', 'Edelsteine und Feingestein', 'Schreibwaren', 'Bücher', 'Magische Artefakte', 'Alchimica', 'Gifte', 'Heilkräuter', 'Musikinstrumente', 'Genussmittel und Luxus', 'Tiere', 'Tierbedarf', 'Forbewegungsmittel'];
export default class InventoryListItem extends Component {
static propTypes = {
add: PropTypes.bool,
data: PropTypes.instanceOf(Item).isRequired,
};
edit = () => InventoryActions.showItemEditor(InventoryStore.get(this.props.data.id));
delete = () => InventoryActions.removeFromList(this.props.data.id);
add = () => InventoryActions.addToList(this.props.data);
render() {
const { add, data: { gr, name, number, price, weight, where, combattechnique, damageDiceNumber, damageDiceSides, damageFlat, damageBonus, at, pa, reach, length, reloadtime, range, ammunition, pro, enc, addpenalties } } = this.props;
const numberValue = number > 1 ? number : null;
return (
<TooltipToggle content={
<div className="inventory-item">
<h4><span>{name}</span><span>{numberValue}</span></h4>
{ gr === 4 ? <p className="ammunition">Munition</p> : null}
{ [4,5].includes(gr) ? <table className="melee">
<tbody>
<tr>
<td>Gewicht</td>
<td>{weight} Stn</td>
</tr>
<tr>
<td>Preis</td>
<td>{price} S</td>
</tr>
</tbody>
</table> : null}
{ gr === 1 ? <table className="melee">
<tbody>
<tr>
<td>Kampftechnik</td>
<td>{get(combattechnique).name}</td>
</tr>
<tr>
<td>TP</td>
<td>{damageDiceNumber}W{damageDiceSides}{damageFlat > 0 ? '+' : null}{damageFlat !== 0 ? damageFlat : null}</td>
</tr>
<tr>
<td>L+S</td>
<td>{get(combattechnique).primary.map(attr => get(attr).short).join('/')} {damageBonus}</td>
</tr>
<tr>
<td>AT/PA-Mod</td>
<td>{at > 0 ? '+' : null}{at}/{pa > 0 ? '+' : null}{pa}</td>
</tr>
<tr>
<td>RW</td>
<td>{['Kurz','Mittel','Lang'][reach - 1]}</td>
</tr>
<tr>
<td>Gewicht</td>
<td>{weight} Stn</td>
</tr>
<tr>
<td>Länge</td>
<td>{length} HF</td>
</tr>
<tr>
<td>Preis</td>
<td>{price} S</td>
</tr>
</tbody>
</table> : null}
{ gr === 2 ? <table className="ranged">
<tbody>
<tr>
<td>Kampftechnik</td>
<td>{get(combattechnique).name}</td>
</tr>
<tr>
<td>TP</td>
<td>{damageDiceNumber}W{damageDiceSides}{damageFlat > 0 ? '+' : null}{damageFlat !== 0 ? damageFlat : null}</td>
</tr>
<tr>
<td>LZ</td>
<td>{reloadtime}</td>
</tr>
<tr>
<td>RW</td>
<td>{range.join('/')}</td>
</tr>
<tr>
<td>Munitionstyp</td>
<td>{(InventoryStore.get(ammunition) || {}).name}</td>
</tr>
<tr>
<td>Gewicht</td>
<td>{weight} Stn</td>
</tr>
<tr>
<td>Länge</td>
<td>{length} HF</td>
</tr>
<tr>
<td>Preis</td>
<td>{price} S</td>
</tr>
</tbody>
</table> : null}
{ gr === 3 ? <table className="armor">
<tbody>
<tr>
<td>RS</td>
<td>{pro}</td>
</tr>
<tr>
<td>BE</td>
<td>{enc}</td>
</tr>
<tr>
<td>Gewicht</td>
<td>{weight} Stn</td>
</tr>
<tr>
<td>Preis</td>
<td>{price} S</td>
</tr>
</tbody>
</table> : null}
{ gr === 3 ? <p className="armor">
Zus. Abzüge: {addpenalties ? '-1 GS, -1 INI' : '-'}
</p> : null}
</div>
} margin={11}>
{add ? (
<tr>
<td className="name">{name}</td>
<td className="inc">
<IconButton
icon="&#xE145;"
onClick={this.add}
/>
</td>
</tr>
):(
<tr>
<td className="type">{GROUPS[gr - 1]}</td>
<td className="number">{numberValue}</td>
<td className="name">{name}</td>
<td className="price">{price} S</td>
<td className="weight">{weight} Stn</td>
<td className="where">{where}</td>
<td className="inc">
<IconButton
icon="&#xE254;"
onClick={this.edit}
/>
<IconButton
icon="&#xE872;"
onClick={this.delete}
/>
</td>
</tr>
)}
</TooltipToggle>
);
}
}
+135 -86
View File
@@ -4,10 +4,17 @@ import Dialog from '../../components/Dialog';
import Dropdown from '../../components/Dropdown';
import Hr from '../../components/Hr';
import IconButton from '../../components/IconButton';
import InventoryActions from '../../actions/InventoryActions';
import InventoryStore from '../../stores/InventoryStore';
import Label from '../../components/Label';
import React, { Component, PropTypes } from 'react';
import TextField from '../../components/TextField';
const GROUPS = ['Nahkampfwaffen', 'Fernkampfwaffen', 'Rüstungen', 'Munition', 'Waffenzubehör', 'Kleidung', 'Reisebedarf und Werkzeuge', 'Beleuchtung', 'Verbandzeug und Heilmittel', 'Behältnisse', 'Seile und Ketten', 'Diebeswerkzeug', 'Handwerkszeug', 'Orientierungshilfen', 'Schmuck', 'Edelsteine und Feingestein', 'Schreibwaren', 'Bücher', 'Magische Artefakte', 'Alchimica', 'Gifte', 'Heilkräuter', 'Musikinstrumente', 'Genussmittel und Luxus', 'Tiere', 'Tierbedarf', 'Forbewegungsmittel'];
const GROUPS_SELECTION = GROUPS.map((e,i) => [ e, i + 1 ]);
// const GROUPS_SELECTION = GROUPS.map((e,i) => [ e, i + 1 ]).sort((a,b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
export default class ItemEditor extends Component {
static propTypes = {
@@ -16,43 +23,53 @@ export default class ItemEditor extends Component {
node: PropTypes.any
};
state = {
state = this.props.item || {
id: '',
name: '',
price: '',
weight: '',
amount: '1',
number: '',
where: '',
gr: 5,
tpl: 'ITEMTPL_0',
ct: 'CT_0', // Combat Technique
dpdn: '', // Number of dices
dpds: null, // Amount of sides per dice
dpf: '', // Flat damage
dpb: '', // Damage bonus with primary attribute
at: '', // AT mod
pa: '', // PA mod
re: 0,
gr: 0,
template: 'ITEMTPL_0',
isTemplateLocked: false,
combattechnique: 'CT_0',
damageDiceNumber: '',
damageDiceSides: null,
damageFlat: '',
damageBonus: '',
at: '',
pa: '',
reach: '',
length: '',
stp: '',
rb1: '', // Range brackets
rb2: '', // Range brackets
rb3: '', // Range brackets
rt: '', // Reload time
am: null, // Ammunition type
pro: '', // Protection
range1: '',
range2: '',
range3: '',
reloadtime: '',
ammunition: null,
pro: '',
enc: '',
addp: false // Add. penalties
addpenalties: false
};
onEvent = (prop, e) => this.setState({ [prop]: e.target.value });
onSwitch = prop => this.setState({ [prop]: !this.state[prop] });
onValue = (prop, value) => this.setState({ [prop]: value });
applyTemplate = () => this.state.template !== 'ITEMTPL_0' && this.setState({ ...InventoryStore.getTemplate(this.state.template), id: this.state.id, isTemplateLocked: false });
lockTemplate = () => this.state.template !== 'ITEMTPL_0' && this.setState({ ...InventoryStore.getTemplate(this.state.template), id: this.state.id });
unlockTemplate = () => this.setState({ isTemplateLocked: false });
addItem = () => InventoryActions.addToList(this.state);
saveItem = () => InventoryActions.saveItem(this.state);
render() {
const { create, node } = this.props;
const { addp, am, amount, at, ct, dpb, dpdn, dpds, dpf, enc, gr, length, name, pa, price, pro, rb1, rb2, rb3, re, rt, stp, tpl, weight, where } = this.state;
const { addpenalties, ammunition, number, at, combattechnique, damageBonus, damageDiceNumber, damageDiceSides, damageFlat, enc, gr, isTemplateLocked: locked, length, name, pa, price, pro, range1, range2, range3, reach, reloadtime, stp, template, weight, where } = this.state;
const TEMPLATES = [['Keine Vorlage', 'ITEMTPL_0']].concat(InventoryStore.getAllTemplates().map(e => [e.name, e.id]).sort((a,b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
return (
<Dialog
@@ -62,9 +79,9 @@ export default class ItemEditor extends Component {
buttons={[
{
label: 'Speichern',
onClick: null,
onClick: create ? this.addItem : this.saveItem,
autoWidth: true,
disabled: true
disabled: name === '' || gr === 0
}
]}>
<div className="main">
@@ -72,14 +89,16 @@ export default class ItemEditor extends Component {
<TextField
className="number"
label="Menge"
value={amount}
onChange={this.onEvent.bind(null, 'amount')}
value={number}
onChange={this.onEvent.bind(null, 'number')}
/>
<TextField
className="name"
label="Name"
value={name}
onChange={this.onEvent.bind(null, 'name')}
autoFocus={create}
disabled={locked}
/>
</div>
<div className="row">
@@ -88,12 +107,14 @@ export default class ItemEditor extends Component {
label="Preis in S"
value={price}
onChange={this.onEvent.bind(null, 'price')}
disabled={locked}
/>
<TextField
className="weight"
label="Gewicht in St"
value={weight}
onChange={this.onEvent.bind(null, 'weight')}
disabled={locked}
/>
<TextField
className="where"
@@ -106,106 +127,120 @@ export default class ItemEditor extends Component {
<Dropdown
className="gr"
label="Art"
hint="Wähle den Typ des Gegenstands aus"
value={gr}
options={[
['Allgemein',5],
['Nahkampfwaffe',1],
['Fernkampfwaffe',2],
['Rüstung',3],
['Munition',4]
]}
options={GROUPS_SELECTION}
onChange={this.onValue.bind(null, 'gr')}
disabled={locked}
/>
</div>
<Hr />
<div className="row">
<Dropdown
className="tpl"
className="template"
label="Vorlage"
hint="Keine"
value={tpl}
options={[]}
onChange={this.onValue.bind(null, 'tpl')}
value={template}
options={TEMPLATES}
onChange={this.onValue.bind(null, 'template')}
disabled={locked}
/>
<IconButton
icon="&#xE876;"
disabled
/>
<IconButton
icon="&#xE876;"
disabled
onClick={this.applyTemplate}
disabled={template === 'ITEMTPL_0' || locked}
/>
{locked ? (
<IconButton
icon="&#xE898;"
onClick={this.unlockTemplate}
/>
) : (
<IconButton
icon="&#xE899;"
onClick={this.lockTemplate}
disabled={template === 'ITEMTPL_0'}
/>
)}
</div>
</div>
{ gr === 1 ? ( <div className="melee">
<Hr />
<div className="row">
<Dropdown
className="ct"
className="combattechnique"
label="Kampftechnik"
hint="Keine"
value={ct}
value={combattechnique}
options={CombatTechniquesStore.getAll().filter(e => e.gr === 1).map(e => [e.name, e.id])}
onChange={this.onValue.bind(null, 'ct')}
disabled={locked}
/>
</div>
<div className="row">
<TextField
className="db"
className="damage-bonus"
label="Schadensb."
value={dpb}
onChange={this.onEvent.bind(null, 'dpb')}
value={damageBonus}
onChange={this.onEvent.bind(null, 'db')}
disabled={locked}
/>
<div className="container">
<Label text="Schaden" />
<Label text="Schaden" disabled={locked} />
<TextField
className="ddn"
value={dpdn}
onChange={this.onEvent.bind(null, 'dpdn')}
className="damage-dice-number"
value={damageDiceNumber}
onChange={this.onEvent.bind(null, 'ddn')}
disabled={locked}
/>
<Dropdown
className="dds"
className="damage-dice-sides"
hint="W"
value={dpds}
value={damageDiceSides}
options={[['W3',3],['W6',6],['W20',20]]}
onChange={this.onValue.bind(null, 'dpds')}
onChange={this.onValue.bind(null, 'dds')}
disabled={locked}
/>
<TextField
className="df"
value={dpf}
onChange={this.onEvent.bind(null, 'dpf')}
className="damage-flat"
value={damageFlat}
onChange={this.onEvent.bind(null, 'df')}
disabled={locked}
/>
</div>
</div>
<div className="row">
<Dropdown
className="re"
className="reach"
label="Reichweite"
hint="Auswählen"
value={re}
value={reach}
options={[['Kurz',1],['Mittel',2],['Lang',3]]}
onChange={this.onValue.bind(null, 're')}
disabled={locked}
/>
<div className="container">
<Label text="AT/PA-Mod" />
<Label text="AT/PA-Mod" disabled={locked} />
<TextField
className="at"
value={at}
onChange={this.onEvent.bind(null, 'at')}
disabled={locked}
/>
<TextField
className="pa"
value={pa}
onChange={this.onEvent.bind(null, 'pa')}
disabled={ct === 'CT_6'}
disabled={locked || combattechnique === 'CT_6'}
/>
</div>
{ ct === 'CT_10' ? (
{ combattechnique === 'CT_10' ? (
<TextField
className="stp"
label="Strukturp."
value={stp}
onChange={this.onEvent.bind(null, 'length')}
disabled={locked}
/>
) : (
<TextField
@@ -213,6 +248,7 @@ export default class ItemEditor extends Component {
label="Länge in Hf."
value={length}
onChange={this.onEvent.bind(null, 'length')}
disabled={locked}
/>
) }
</div>
@@ -221,78 +257,88 @@ export default class ItemEditor extends Component {
<Hr />
<div className="row">
<Dropdown
className="ct"
className="combattechnique"
label="Kampftechnik"
hint="Keine"
value={ct}
value={combattechnique}
options={CombatTechniquesStore.getAll().filter(e => e.gr === 2).map(e => [e.name, e.id])}
onChange={this.onValue.bind(null, 'ct')}
disabled={locked}
/>
</div>
<div className="row">
<TextField
className="rt"
className="reloadtime"
label="Ladezeiten"
value={rt}
value={reloadtime}
onChange={this.onEvent.bind(null, 'rt')}
disabled={locked}
/>
<div className="container">
<Label text="Schaden" />
<Label text="Schaden" disabled={locked} />
<TextField
className="ddn"
value={dpdn}
onChange={this.onEvent.bind(null, 'dpdn')}
className="damage-dice-number"
value={damageDiceNumber}
onChange={this.onEvent.bind(null, 'ddn')}
disabled={locked}
/>
<Dropdown
className="dds"
className="damage-dice-sides"
hint="W"
value={dpds}
value={damageDiceSides}
options={[['W3',3],['W6',6],['W20',20]]}
onChange={this.onValue.bind(null, 'dpds')}
onChange={this.onValue.bind(null, 'dds')}
disabled={locked}
/>
<TextField
className="df"
value={dpf}
onChange={this.onEvent.bind(null, 'dpf')}
className="damage-flat"
value={damageFlat}
onChange={this.onEvent.bind(null, 'df')}
disabled={locked}
/>
</div>
</div>
<div className="row">
<div className="container">
<TextField
className="rb1"
className="range1"
label="Nah"
value={rb1}
value={range1}
onChange={this.onValue.bind(null, 'rb1')}
disabled={locked}
/>
<TextField
className="rb2"
className="range2"
label="Mittel"
value={rb2}
value={range2}
onChange={this.onValue.bind(null, 'rb2')}
disabled={locked}
/>
<TextField
className="rb3"
className="range3"
label="Weit"
value={rb3}
value={range3}
onChange={this.onValue.bind(null, 'rb3')}
disabled={locked}
/>
</div>
<Dropdown
className="am"
className="ammunition"
label="Munition"
hint="Keine"
value={am}
value={ammunition}
options={[
['Keine',null]
]}
onChange={this.onValue.bind(null, 'am')}
disabled={locked}
/>
<TextField
className="length"
label="Länge in Hf."
value={length}
onChange={this.onEvent.bind(null, 'length')}
disabled={locked}
/>
</div>
</div> ) : null }
@@ -305,19 +351,22 @@ export default class ItemEditor extends Component {
label="RS"
value={pro}
onChange={this.onEvent.bind(null, 'pro')}
disabled={locked}
/>
<TextField
className="enc"
label="BE"
value={enc}
onChange={this.onEvent.bind(null, 'enc')}
disabled={locked}
/>
</div>
<Checkbox
className="addp"
className="addpenalties"
label="Zusätzliche Abzüge"
checked={addp}
onClick={this.onSwitch.bind(null, 'addp')}
checked={addpenalties}
onClick={this.onSwitch.bind(null, 'addpenalties')}
disabled={locked}
/>
</div>
</div> ) : null }
+5 -12
View File
@@ -76,15 +76,15 @@
}
> div.container > div {
&.ddn, &.dds, &.df, &.at, &.pa, &.rb1, &.rb2, &.rb3, &.pro, &.enc {
&.damage-dice-number, &.damage-dice-sides, &.damage-flat, &.at, &.pa, &.range1, &.range2, &.range3, &.pro, &.enc {
flex: none;
}
&.dds {
&.damage-dice-sides {
width: 65px;
}
&.ddn, &.df, &.at, &.pa, &.rb1, &.rb2, &.rb3, &.pro, &.enc {
&.damage-dice-sides, &.damage-flat, &.at, &.pa, &.range1, &.range2, &.range3, &.pro, &.enc {
width: 40px;
input {
@@ -95,15 +95,8 @@
}
}
hr {
margin: 15px 0 5px;
border: none;
height: 1px;
background: $color9;
&:first-child {
margin-top: 5px;
}
hr:first-child {
margin-top: 5px;
}
}
}
+61 -1
View File
@@ -1 +1,61 @@
@import "ItemEditor";
@import "ItemEditor";
.page {
&#inventory {
tbody {
td:last-child {
padding-right: 11px;
}
td.inc > .btn {
height: 32px;
padding: 2px;
}
}
}
}
.overlay > .inventory-item {
max-width: 280px;
h4 {
span:first-child {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
}
table {
margin-top: 15px;
font: 500 13px/22px Alegreya Sans;
letter-spacing: 0.05em;
td {
padding: 0;
border: none;
height: auto;
&:first-child {
color: $color2;
padding-right: 15px;
width: 0;
}
&:last-child {
color: $color1;
}
}
}
p.ammunition {
margin-top: 3px;
}
p.armor {
margin-top: 10px;
font: 500 13px/22px Alegreya Sans;
color: $color1;
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ export default class CombatTechniques extends Component {
]} />
</div>
<Scroll>
<table>
<table className="list">
<thead>
<tr>
<td className="type">Gruppe</td>
+2 -2
View File
@@ -87,7 +87,7 @@ export default class Liturgies extends Component {
/>
</div>
<Scroll className="list">
<table>
<table className="list">
<thead>
<tr>
<td className="type">Gruppe</td>
@@ -146,7 +146,7 @@ export default class Liturgies extends Component {
/>
</div>
<Scroll className="list">
<table>
<table className="list">
<thead>
<tr>
<td className="type">Gruppe</td>
+2 -2
View File
@@ -69,7 +69,7 @@ export default class SpecialAbilities extends Component {
/>
</div>
<Scroll className="list">
<table>
<table className="list large-list">
<thead>
<tr>
<td className="type">Gruppe</td>
@@ -96,7 +96,7 @@ export default class SpecialAbilities extends Component {
<BorderButton label="Hinzufügen" onClick={this.showAddSlidein} />
</div>
<Scroll className="list">
<table>
<table className="list large-list">
<thead>
<tr>
<td className="type">Gruppe</td>
+2 -2
View File
@@ -96,7 +96,7 @@ export default class Spells extends Component {
/>
</div>
<Scroll className="list">
<table>
<table className="list">
<thead>
<tr>
<td className="type">Gruppe</td>
@@ -156,7 +156,7 @@ export default class Spells extends Component {
/>
</div>
<Scroll className="list">
<table>
<table className="list">
<thead>
<tr>
<td className="type">Gruppe</td>
+1 -1
View File
@@ -62,7 +62,7 @@ export default class Talents extends Component {
<Checkbox checked={talentRating} onClick={this.changeTalentRating}>Wertung durch Kultur anzeigen</Checkbox>
</div>
<Scroll className="list">
<table>
<table className="list">
<thead>
<tr>
<td className="type">Gruppe</td>