Compare commits

...

6 Commits
1.2.0 ... 1.2.3

Author SHA1 Message Date
Stefan Zermatten
abcfe57add Fixed results not being used when popping dialogs
Fixes #66
2017-05-11 11:08:00 +02:00
Stefan Zermatten
cdae75beef Merge branch 'bugfix-iamastick' 2017-05-10 16:40:05 +02:00
Stefan Zermatten
e7bcc2224c Replaced effects in-line editing with edit dialogs
Closes #41
2017-05-10 16:39:36 +02:00
Stefan Zermatten
b591c66dd5 Back button now closes dialogs
closes #9
2017-05-10 10:38:28 +02:00
Stefan Zermatten
402bc0e5ed Fixed Readme 2017-05-10 10:37:30 +02:00
Stefan Zermatten
ac772531ee Fixed SRD spells not getting a random ID when inserted
fixes #64
fixes #65
2017-05-08 11:21:30 +02:00
17 changed files with 357 additions and 93 deletions

View File

@@ -1,13 +1,13 @@
RPG Docs RPG Docs
======== ========
This is the repo for [DiceCloud](dicecloud.com). The currently deployed version should always be the latest release of the master branch. This is the repo for [DiceCloud](dicecloud.com).
Getting started Getting started
--------------- ---------------
`git clone https://github.com/ThaumRystra/RPG-Docs RPG-Docs` `git clone https://github.com/ThaumRystra/DiceCloud1 dicecloud`
`cd RPG-Docs` `cd dicecloud`
`cd rpg-docs` `cd rpg-docs`
`bower install` `bower install`
`meteor` `meteor`

View File

@@ -13,3 +13,13 @@
.effectEdit .deleteEffect { .effectEdit .deleteEffect {
flex-shrink: 0; flex-shrink: 0;
} }
.effectEdit .effect-table-view {
align-self: center;
color: #757575;
color: rgba(0,0,0,0.54);
}
.effectEdit .iron-selected {
color: #ad2a1f;
}

View File

@@ -1,45 +1,53 @@
<template name="effectEdit"> <template name="effectEdit">
<div class="effectEdit layout horizontal center"> {{#with effect}}
<paper-dropdown-menu label="Stat" class="statDropDown" dynamic-align> {{#baseEditDialog hideColor=true title="Effect"}}
<dicecloud-selector class="statMenu dropdown-content" selected={{stat}} selectable="paper-item"> <div class="fit layout vertical effectEdit" style="padding: 24px;">
{{#each statGroups}} <table class="paper-font-display1 effect-table-view">
<div style="font-weight: bold; margin-top: 16px; padding-left: 8px;"> <tr>
{{this}} {{> effectView}}
</div> </tr>
{{#each stats}} </table>
<paper-item name={{stat}}>{{name}}</paper-item> <hr class="vertMargin" style="width: 100%;">
{{/each}} {{#if showEffectValueInput}}
{{/each}} <paper-input class="effectValueInput"
</dicecloud-selector> label="Value"
</paper-dropdown-menu> floatinglabel
{{#if operations}} value={{effectValue}}>
<paper-dropdown-menu class="operationDropDown" label="Operation" dynamic-align> </paper-input>
<dicecloud-selector class="dropdown-content operationMenu" selected={{operation}}> {{else}}
{{#each operations}} <div style="height: 62px;"></div>
<paper-item name={{operation}}>{{name}}</paper-item> {{/if}}
<div class="effectEdit layout horizontal flex">
<dicecloud-selector class="statMenu flex" selected={{stat}} selectable="paper-item" style="height: 100%; overflow-y: auto;">
{{#each statGroups}}
<div style="font-weight: bold; margin-top: 16px; padding-left: 8px;">
{{this}}
</div>
{{#each stats}}
<paper-item name={{stat}}>{{name}}</paper-item>
{{/each}}
{{/each}} {{/each}}
</dicecloud-selector> </dicecloud-selector>
</paper-dropdown-menu> {{#if operations}}
{{/if}} <dicecloud-selector class="operationMenu flex" selected={{operation}} style="height: 100%; overflow-y: auto;">
{{#if effectValueTemplate}} {{#each operations}}
{{> Template.dynamic template=effectValueTemplate}} <paper-item name={{operation}}>{{name}}</paper-item>
{{else}} {{/each}}
<div class="flex"></div> </dicecloud-selector>
{{/if}} {{else}} {{#if showMultiplierOperations}}
<paper-icon-button class="deleteEffect" <dicecloud-selector class="multiplierMenu flex"
icon="delete"> selected={{value}}>
</paper-icon-button> <paper-item name="0.5">Resistance</paper-item>
<br> <paper-item name="2">Vulnerability</paper-item>
</div> <paper-item name="0">Immunity</paper-item>
</template> </dicecloud-selector>
{{else}}
<template name="regularEffectValue"> <div class="flex" style="height: 100%;"></div>
<paper-input class="effectValueInput flex" {{/if}} {{/if}}
label="Value" </div>
floatinglabel </div>
value={{effectValue}} {{/baseEditDialog}}
style="flex-basis: 100px;"> {{/with}}
</paper-input>
</template> </template>
<template name="multiplierEffectValue"> <template name="multiplierEffectValue">
@@ -51,5 +59,4 @@
<paper-item name="0">Immunity</paper-item> <paper-item name="0">Immunity</paper-item>
</dicecloud-selector> </dicecloud-selector>
</paper-dropdown-menu> </paper-dropdown-menu>
<div class="flex"></div>
</template> </template>

View File

@@ -97,7 +97,19 @@ var skillOperations = [
{name: "Conditional Benefit", operation: "conditional"}, {name: "Conditional Benefit", operation: "conditional"},
]; ];
Template.effectEdit.onRendered(function(){
_.defer(() => {
const statElement = this.find(".statMenu .iron-selected");
statElement && statElement.scrollIntoView();
const opElement = this.find(".operationMenu .iron-selected");
opElement && opElement.scrollIntoView();
});
});
Template.effectEdit.helpers({ Template.effectEdit.helpers({
effect: function(){
return Effects.findOne(this.id);
},
statGroups: function(){ statGroups: function(){
return statGroupNames; return statGroupNames;
}, },
@@ -115,46 +127,77 @@ Template.effectEdit.helpers({
return attributeOperations; return attributeOperations;
} }
}, },
effectValueTemplate: function(){ showMultiplierOperations: function(){
//resistance/vulnerability template var stat = statsDict[this.stat];
return stat && stat.group === "Weakness/Resistance"
},
showEffectValueInput: function(){
var stat = statsDict[this.stat]; var stat = statsDict[this.stat];
var group = stat && stat.group; var group = stat && stat.group;
if (group === "Weakness/Resistance") return "multiplierEffectValue"; if (
group === "Weakness/Resistance"
) return false;
var op = this.operation; var op = this.operation;
if (!op) return null; if (
//operations that don't need templates !op ||
if (op === "advantage" || op === "disadvantage" || op === "fail") return null; op === "advantage" ||
op === "disadvantage" ||
//default template op === "fail"
return "regularEffectValue"; ) return false;
return true;
}, },
});
Template.regularEffectValue.helpers({
effectValue: function(){ effectValue: function(){
return this.calculation || this.value; return this.calculation || this.value;
} },
}); });
Template.effectEdit.events({ Template.effectEdit.events({
"click .deleteEffect": function(event){ "click #deleteButton": function(event, instance){
Effects.softRemoveNode(this._id); Effects.softRemoveNode(instance.data.id);
GlobalUI.deletedToast(this._id, "Effects", "Effect"); GlobalUI.deletedToast(instance.data.id, "Effects", "Effect");
popDialogStack();
}, },
"iron-select .statDropDown": function(event){ "iron-select .statMenu": function(event){
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;
var statName = detail.item.getAttribute("name"); var statName = detail.item.getAttribute("name");
if (statName == this.stat) return; if (statName == this.stat) return;
Effects.update(this._id, {$set: {stat: statName}}); var setter = {stat: statName};
var group = Blaze.getData(detail.item).group;
var effect = Effects.findOne(this._id);
if (group === "Saving Throws" || group === "Skills"){
// Skills must have a valid skill operation
if (!_.contains(
_.map(skillOperations, ao => ao.operation),
effect.operation
)){
setter.operation = "add";
}
} else if (group !== "Weakness/Resistance"){
// Attributes must have a valid attribute operation
if (!_.contains(
_.map(attributeOperations, ao => ao.operation),
effect.operation
)){
setter.operation = "base";
}
} else {
// Weakness/Resistance must have a mul operation and value
if (effect.operation !== "mul"){
setter.operation = "mul";
}
if (!_.contains([0, 0.5, 2], effect.value)){
setter.value = 0.5;
}
}
Effects.update(this._id, {$set: setter});
}, },
"iron-select .operationDropDown": function(event){ "iron-select .operationMenu": function(event){
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;
var opName = detail.item.getAttribute("name"); var opName = detail.item.getAttribute("name");
if (opName == this.operation) return; if (opName == this.operation) return;
Effects.update(this._id, {$set: {operation: opName}}); Effects.update(this._id, {$set: {operation: opName}});
}, },
"iron-select .damageMultiplierDropDown": function(event){ "iron-select .multiplierMenu": function(event){
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;
var value = +detail.item.getAttribute("name"); var value = +detail.item.getAttribute("name");
if (value == this.value) return; if (value == this.value) return;
@@ -164,15 +207,25 @@ Template.effectEdit.events({
operation: "mul", operation: "mul",
}}); }});
}, },
"change .effectValueInput": function(event){ "change .effectValueInput, input .effectValueInput":
_.debounce(function(event){
var value = event.currentTarget.value; var value = event.currentTarget.value;
var numValue = +value; var numValue = value === "" ? NaN : +value;
if (_.isFinite(numValue)){ if (_.isFinite(numValue)){
if (this.value === numValue) return; if (this.value === numValue) return;
Effects.update(this._id, {$set: {value: numValue, calculation: ""}}); Effects.update(this._id, {
$set: {value: numValue},
$unset: {calculation: ""},
});
} else if (_.isString(value)){ } else if (_.isString(value)){
if (this.calculation === value) return; if (this.calculation === value) return;
Effects.update(this._id, {$set: {value: "", calculation: value}}); Effects.update(this._id, {
$set: {calculation: value},
$unset: {value: ""},
}, {
removeEmptyStrings: false,
trimStrings: false,
});
} }
}, }, 400),
}); });

View File

@@ -1,6 +1,4 @@
<template name="effectView"> <template name="effectView">
<tr> <td>{{statName}}</td>
<td>{{statName}}</td> <td>{{operationName}}{{statValue}}</td>
<td>{{operationName}}{{statValue}}</td>
</tr>
</template> </template>

View File

@@ -1,4 +1,3 @@
//TODO add dexterity armor
var stats = { var stats = {
"strength":{"name":"Strength"}, "strength":{"name":"Strength"},
"dexterity":{"name":"Dexterity"}, "dexterity":{"name":"Dexterity"},
@@ -131,8 +130,10 @@ Template.effectView.helpers({
return stats[this.stat] && stats[this.stat].name || "No Stat"; return stats[this.stat] && stats[this.stat].name || "No Stat";
}, },
operationName: function(){ operationName: function(){
if (this.operation === "proficiency" || if (
this.operation === "conditional") return null; this.operation === "proficiency" ||
this.operation === "conditional"
) return null;
if (stats[this.stat] && stats[this.stat].group === "Weakness/Resistance") if (stats[this.stat] && stats[this.stat].group === "Weakness/Resistance")
return null; return null;
if (this.operation === "add" && evaluateEffect(this.charId, this) < 0) if (this.operation === "add" && evaluateEffect(this.charId, this) < 0)
@@ -141,9 +142,11 @@ Template.effectView.helpers({
operations[this.operation].name || "No Operation"; operations[this.operation].name || "No Operation";
}, },
statValue: function(){ statValue: function(){
if (this.operation === "advantage" || if (
this.operation === "disadvantage" || this.operation === "advantage" ||
this.operation === "fail"){ this.operation === "disadvantage" ||
this.operation === "fail"
){
return null; return null;
} }
if (this.operation === "proficiency"){ if (this.operation === "proficiency"){

View File

@@ -0,0 +1,3 @@
.effectsEditList .effect {
background: white;
}

View File

@@ -1,11 +1,19 @@
<!--needs to be given charId, parentId and parentCollection--> <!--needs to be given charId, parentId and parentCollection-->
<template name="effectsEditList"> <template name="effectsEditList">
{{#if effects.count}} {{#if effects.length}}
<div id="effects"> <div class="effectsEditList">
<div class="paper-font-title">Effects</div> <div class="paper-font-title">Effects</div>
{{#each effects}} <table class="wideTable" style="width: 100%;">
{{>effectEdit}} {{#each effects}}
{{/each}} <tr class="effect" data-id={{_id}}>
{{>effectView}}
<td>
<paper-icon-button class="edit-effect" icon="create">
</paper-icon-button>
</td>
</tr>
{{/each}}
</table>
</div> </div>
{{/if}} {{/if}}
<paper-button id="addEffectButton" class="red-button" raised>Add Effect</paper-button> <paper-button id="addEffectButton" class="red-button" raised>Add Effect</paper-button>

View File

@@ -8,17 +8,17 @@ Template.effectsEditList.helpers({
if (this.parentGroup){ if (this.parentGroup){
selector["parent.group"] = this.parentGroup; selector["parent.group"] = this.parentGroup;
} }
var cursor = Effects.find(selector); var effects = Effects.find(selector).fetch();
return cursor; return _.sortBy(effects, effect => statOrder[effect.stat] || 999);
} }
}); });
Template.effectsEditList.events({ Template.effectsEditList.events({
"tap #addEffectButton": function(){ "tap #addEffectButton": function(event, instance){
if (!_.isBoolean(this.enabled)) { if (!_.isBoolean(this.enabled)) {
this.enabled = true; this.enabled = true;
} }
Effects.insert({ const effectId = Effects.insert({
name: this.name, name: this.name,
charId: this.charId, charId: this.charId,
parent: { parent: {
@@ -29,5 +29,18 @@ Template.effectsEditList.events({
operation: "add", operation: "add",
enabled: this.enabled, enabled: this.enabled,
}); });
pushDialogStack({
template: "effectEdit",
data: {id: effectId},
element: event.currentTarget,
returnElement: instance.find(`tr.effect[data-id='${effectId}']`),
});
},
"tap .edit-effect": function(event, template){
pushDialogStack({
template: "effectEdit",
data: {id: this._id},
element: event.currentTarget.parentElement.parentElement,
});
}, },
}); });

View File

@@ -1,11 +1,11 @@
<!--needs to be given charId, (parentId or stat) and type--> <!--needs to be given charId, (parentId or stat) and type-->
<template name="effectsViewList"> <template name="effectsViewList">
{{#if effects.count}} {{#if effects.length}}
<div class="effects"> <div class="effects">
<div class="spaceAfter paper-font-title">Effects</div> <div class="spaceAfter paper-font-title">Effects</div>
<table class="wideTable"> <table class="wideTable">
{{#each effects}} {{#each effects}}
{{>effectView}} <tr>{{>effectView}}</tr>
{{/each}} {{/each}}
</table> </table>
</div> </div>

View File

@@ -2,11 +2,14 @@ Template.effectsViewList.helpers({
effects: function(){ effects: function(){
var selector = { var selector = {
"parent.id": this.parentId, "parent.id": this.parentId,
"charId": this.charId "charId": this.charId,
}; };
if (this.parentGroup){ if (this.parentGroup){
selector["parent.group"] = this.parentGroup; selector["parent.group"] = this.parentGroup;
} }
return Effects.find(selector, {fields: {parent: 0}}); let effects = Effects.find(selector, {
fields: {parent: 0},
}).fetch();
return _.sortBy(effects, effect => statOrder[effect.stat] || 999);
} }
}); });

View File

@@ -258,6 +258,7 @@ Template.spells.events({
} }
// Make the library spell into a regular spell // Make the library spell into a regular spell
let spell = _.omit(result, "library", "attacks", "effects"); let spell = _.omit(result, "library", "attacks", "effects");
spell._id = spellId;
spell.charId = charId; spell.charId = charId;
spell.parent = { spell.parent = {
id: listId, id: listId,

View File

@@ -27,7 +27,7 @@
{{/if}} {{/if}}
{{/if}} {{/if}}
</app-toolbar> </app-toolbar>
<div class="form flex scroll-y"> <div class="form flex scroll-y" style="position: relative;">
{{#unless editing}} {{#unless editing}}
{{> UI.contentBlock}} {{> UI.contentBlock}}
{{else}} {{else}}

View File

@@ -0,0 +1,23 @@
<template name="baseEditDialog">
<div class="fit base-dialog layout vertical">
<app-toolbar class={{class}}>
<paper-icon-button id="backButton"
icon="arrow-back">
</paper-icon-button>
<div main-title>{{title}}</div>
{{#unless hideDelete}}
<paper-icon-button id="deleteButton"
role="button"
tabindex="0"
icon="delete">
</paper-icon-button>
{{/unless}}
{{#unless hideColor}}
{{> colorDropdown}}
{{/unless}}
</app-toolbar>
<div class="form flex scroll-y" style="position: relative;">
{{> UI.contentBlock}}
</div>
</div>
</template>

View File

@@ -0,0 +1,5 @@
Template.baseEditDialog.events({
"tap #backButton": function(){
popDialogStack();
},
});

View File

@@ -13,14 +13,80 @@ pushDialogStack = function({template, data, element, returnElement, callback}){
returnElement, returnElement,
callback, callback,
}); });
updateHistory();
}; };
var currentResult;
popDialogStack = function(result){ popDialogStack = function(result){
if (history && history.state && history.state.openDialogs){
currentResult = result;
history.back();
} else {
popDialogStackAction(result);
}
}
window.onpopstate = function(event){
let state = event.state;
let numDialogs = dialogs._array.length;
if (_.isFinite(state.openDialogs) && numDialogs > state.openDialogs){
popDialogStackAction(currentResult);
currentResult = undefined;
}
}
popDialogStackAction = function(result){
const dialog = dialogs.pop(); const dialog = dialogs.pop();
updateHistory();
if (!dialog) return; if (!dialog) return;
dialog.callback && dialog.callback(result); dialog.callback && dialog.callback(result);
}; };
let updateHistory = function(){
// history should looks like: [{openDialogs: 0}, {openDialogs: n}] where
// n is the number of open dialogs
// If we can't access the history object, give up
if (!history) return;
// Make sure that there is a state tracking open dialogs
// replace the state without bashing it in the process
if (!history.state || !_.isFinite(history.state.openDialogs)){
let newState = _.clone(history.state) || {};
newState.openDialogs = 0;
history.replaceState(newState, "");
}
const numDialogs = dialogs._array.length;
const stateDialogs = history.state.openDialogs;
// If the number of dialogs and state dialogs are equal, we don't need to do
// anything
if (numDialogs === stateDialogs) return;
if (stateDialogs > 0){
// On a dialog count
if (numDialogs === 0){
// but shouldn't be
history.back();
} else {
// but should replace with correct count
let newState = _.clone(history.state) || {};
newState.openDialogs = dialogs._array.length;
history.replaceState(newState, "");
}
} else if (numDialogs > 0 && stateDialogs === 0){
// On the zero state, push a dialog count
history.pushState({openDialogs: numDialogs}, "");
} else {
console.warn(
"History could not be updated correctly, unexpected case",
{stateDialogs, numDialogs},
)
}
};
Template.dialogStack.helpers({ Template.dialogStack.helpers({
dialogStackClass(){ dialogStackClass(){
if (!dialogs.get().length) return "hide"; if (!dialogs.get().length) return "hide";

View File

@@ -0,0 +1,71 @@
statOrder = {
"strength": 1,
"dexterity": 2,
"constitution": 3,
"intelligence": 4,
"wisdom": 5,
"charisma": 6,
"strengthSave": 7,
"dexteritySave": 8,
"constitutionSave": 9,
"intelligenceSave": 10,
"wisdomSave": 11,
"charismaSave": 12,
"acrobatics": 13,
"animalHandling": 14,
"arcana": 15,
"athletics": 16,
"deception": 17,
"history": 18,
"insight": 19,
"intimidation": 20,
"investigation": 21,
"medicine": 22,
"nature": 23,
"perception": 24,
"performance": 25,
"persuasion": 26,
"religion": 27,
"sleightOfHand": 28,
"stealth": 29,
"survival": 30,
"initiative": 31,
"hitPoints": 32,
"armor": 33,
"dexterityArmor": 34,
"speed": 35,
"proficiencyBonus": 36,
"ki": 37,
"sorceryPoints": 38,
"rages": 39,
"rageDamage": 40,
"expertiseDice": 41,
"superiorityDice": 42,
"carryMultiplier": 43,
"level1SpellSlots": 44,
"level2SpellSlots": 45,
"level3SpellSlots": 46,
"level4SpellSlots": 47,
"level5SpellSlots": 48,
"level6SpellSlots": 49,
"level7SpellSlots": 50,
"level8SpellSlots": 51,
"level9SpellSlots": 52,
"d6HitDice": 53,
"d8HitDice": 54,
"d10HitDice": 55,
"d12HitDice": 56,
"acidMultiplier": 57,
"bludgeoningMultiplier": 58,
"coldMultiplier": 59,
"fireMultiplier": 60,
"forceMultiplier": 61,
"lightningMultiplier": 62,
"necroticMultiplier": 63,
"piercingMultiplier": 64,
"poisonMultiplier": 65,
"psychicMultiplier": 66,
"radiantMultiplier": 67,
"slashingMultiplier": 68,
"thunderMultiplier": 69,
};