Compare commits

..

13 Commits
1.1.1 ... 1.2.2

Author SHA1 Message Date
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
Stefan Zermatten
9a3edb07e4 Merge branch 'feature-spell-library' 2017-05-05 11:12:59 +02:00
Stefan Zermatten
fea45d2398 Fixed spell data source 2017-05-05 11:11:56 +02:00
Stefan Zermatten
18aa1b2040 Fixed publications of spell library 2017-05-05 11:11:43 +02:00
Stefan Zermatten
9afe38d043 removed stray log 2017-05-05 10:45:08 +02:00
Stefan Zermatten
5ddbecf97e Added spells library 2017-05-05 10:45:07 +02:00
Stefan Zermatten
7b573ca86e Fixed some dialogs getting stuck on close animation, poisoning future dialogs
closes #63
2017-05-05 10:45:05 +02:00
Stefan Zermatten
a9256fed05 Fixed some dialogs getting stuck on close animation, poisoning future dialogs
closes #63
2017-05-05 10:44:16 +02:00
Stefan Zermatten
8ec10e7a6a Added SRD spell data 2017-05-04 16:19:21 +02:00
26 changed files with 5441 additions and 97 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`

4739
dataSources/srd/spells.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -10,3 +10,8 @@ _.each(items, (item) => {
item.library = "SRDLibraryGA3XWsd" item.library = "SRDLibraryGA3XWsd"
LibraryItems.insert(item) LibraryItems.insert(item)
}); });
_.each(spells, (spell) => {
spell.library = "SRDLibraryGA3XWsd"
LibrarySpells.insert(spell)
});

View File

@@ -0,0 +1,66 @@
LibrarySpells = new Mongo.Collection("librarySpells");
Schemas.LibrarySpells = new SimpleSchema({
name: {
type: String,
trim: false,
defaultValue: "New Spell",
},
description: {
type: String,
optional: true,
trim: false,
},
castingTime: {
type: String,
optional: true,
defaultValue: "action",
trim: false,
},
range: {
type: String,
optional: true,
trim: false,
},
duration: {
type: String,
optional: true,
trim: false,
defaultValue: "Instantaneous",
},
"components.verbal": {type: Boolean, defaultValue: false},
"components.somatic": {type: Boolean, defaultValue: false},
"components.concentration": {type: Boolean, defaultValue: false},
"components.material": {type: String, optional: true},
ritual: {
type: Boolean,
defaultValue: false,
},
level: {
type: Number,
defaultValue: 1,
},
school: {
type: String,
defaultValue: "Abjuration",
allowedValues: magicSchools,
},
library: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
effects: {type: [Schemas.LibraryEffects], defaultValue: []},
attacks: {type: [Schemas.LibraryAttacks], defaultValue: []},
});
LibrarySpells.attachSchema(Schemas.LibrarySpells);
LibrarySpells.allow({
insert(userId, doc) {
return Libraries.canEdit(userId, doc.library);
},
update(userId, doc, fields, modifier) {
return Libraries.canEdit(userId, doc.library);
},
remove(userId, doc) {
return Libraries.canEdit(userId, doc.library);
},
fetch: ["library"],
});

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

@@ -29,7 +29,6 @@ Template.newCharacterDialog.helpers({
changeFunction = function(field){ changeFunction = function(field){
return _.debounce(function(event, instance){ return _.debounce(function(event, instance){
console.log({field, event})
instance.character[field] = event.currentTarget.value; instance.character[field] = event.currentTarget.value;
instance.schema.clean(instance.character); instance.schema.clean(instance.character);
instance.context.validate(instance.character); instance.context.validate(instance.character);

View File

@@ -131,6 +131,15 @@
</paper-fab> </paper-fab>
</div> </div>
<div>
<paper-tooltip position="left">
Spell library
</paper-tooltip>
<paper-fab icon="av:library-books"
class="librarySpell"
mini>
</paper-fab>
</div>
<div> <div>
<paper-tooltip position="left"> <paper-tooltip position="left">
New spell New spell

View File

@@ -211,8 +211,17 @@ Template.spells.events({
}); });
}, },
"click .addSpell": function(event, instance){ "click .addSpell": function(event, instance){
var charId = this.charId; var charId = this._id;
var listId = SpellLists.findOne({charId: this._id})._id; var list = SpellLists.findOne({charId});
var listId = list && list._id
if (!listId){
listId = SpellLists.insert({
name: "New SpellList",
charId: charId,
saveDC: "8 + intelligenceMod + proficiencyBonus",
attackBonus: "intelligenceMod + proficiencyBonus",
});
}
var id = Spells.insert({ var id = Spells.insert({
name: "New Spell", name: "New Spell",
charId: this._id, charId: this._id,
@@ -229,6 +238,49 @@ Template.spells.events({
returnElement: () => instance.find(`.spell[data-id='${id}']`), returnElement: () => instance.find(`.spell[data-id='${id}']`),
}); });
}, },
"click .librarySpell": function(event, instance){
var charId = this._id;
var spellId = Random.id();
var list = SpellLists.findOne({charId});
var listId = list && list._id
pushDialogStack({
template: "spellLibraryDialog",
element: event.currentTarget,
callback: (result) => {
if (!result) return;
if (!listId){
listId = SpellLists.insert({
name: "New SpellList",
charId: charId,
saveDC: "8 + intelligenceMod + proficiencyBonus",
attackBonus: "intelligenceMod + proficiencyBonus",
});
}
// Make the library spell into a regular spell
let spell = _.omit(result, "library", "attacks", "effects");
spell._id = spellId;
spell.charId = charId;
spell.parent = {
id: listId,
collection: "SpellLists",
};
spell.prepared = "prepared";
Spells.insert(spell);
// Copy over attacks and effects
_.each(result.attacks, (attack) => {
attack.charId = charId;
attack.parent = {id: spellId, collection: "Spells"};
Attacks.insert(attack);
});
_.each(result.effects, (effect) => {
effect.charId = charId;
effect.parent = {id: spellId, collection: "Spells"};
Effects.insert(effect);
});
},
returnElement: () => $(`[data-id='${spellId}']`).get(0),
})
},
"click .preparedCheckbox": function(event){ "click .preparedCheckbox": function(event){
event.stopPropagation(); event.stopPropagation();
}, },

View File

@@ -0,0 +1,23 @@
.spell-library-dialog .spell.selected {
background-color: #e4e4e4;
}
.spell-library-dialog .category-header {
font-size: 16px;
}
.spell-library-dialog .category-header iron-icon {
transition: transform 0.3s ease;
}
.spell-library-dialog .category-header iron-icon.open {
transform: rotate(90deg);
}
.spell-library-dialog table {
border-collapse: collapse;
}
.spell-library-dialog .library-spell td {
position: relative;
}

View File

@@ -0,0 +1,68 @@
<template name="spellLibraryDialog">
<div class="fit spell-library-dialog layout vertical">
<app-toolbar class="app-grey white-text">
<paper-icon-button id="backButton"
icon="arrow-back">
</paper-icon-button>
<div main-title>Spells</div>
<paper-input label="Search" class="search-input">
<iron-icon icon="search" prefix></iron-icon>
</paper-input>
</app-toolbar>
<div class="flex scroll-y">
<div class="spells" style="padding:8px">
{{#if searchTerm}}
{{#if searchSpells.count}}
<table style="width: 100%">
<tbody>
{{#each spell in searchSpells}}
{{>librarySpell spell=spell selected=(isSelected spell)}}
{{/each}}
</tbody>
</table>
{{else}}{{#if searchReady}}
No spells match "{{searchTerm}}"
{{/if}}{{/if}}
{{#unless searchReady}}
<div class="layout vertical center" style="width: 100%; padding: 16px;">
<paper-spinner active></paper-spinner>
</div>
{{/unless}}
{{else}}
{{#each categories}}
<div class="paper-font-body2 category-header clickable">
<iron-icon icon="chevron-right" class="{{#if isOpen key}}open{{/if}}">
</iron-icon>
{{name}}
</div>
<iron-collapse opened={{isOpen key}}>
<table style="width: 100%">
<tbody>
{{#each spell in (spellsInCategory key)}}
{{>librarySpell spell=spell selected=(isSelected spell)}}
{{/each}}
</tbody>
</table>
{{#unless ready key}}
<paper-spinner active></paper-spinner>
{{/unless}}
</iron-collapse>
{{/each}}
{{/if}}
</div>
</div>
<div class="layout horizontal end-justified">
<paper-button class="cancelButton">Cancel</paper-button>
<paper-button class="okButton">OK</paper-button>
</div>
</div>
</template>
<template name="librarySpell">
<tr class="spell library-spell {{#if selected}}selected{{/if}}">
<td class="spellName">
{{spell.name}}
<paper-ripple></paper-ripple>
</td>
</tr>
</template>

View File

@@ -0,0 +1,115 @@
const librarySubs = new SubsManager();
const categories = [
{name: "Cantrips", key: 0},
{name: "Level 1", key: 1},
{name: "Level 2", key: 2},
{name: "Level 3", key: 3},
{name: "Level 4", key: 4},
{name: "Level 5", key: 5},
{name: "Level 6", key: 6},
{name: "Level 7", key: 7},
{name: "Level 8", key: 8},
{name: "Level 9", key: 9},
];
Template.spellLibraryDialog.onCreated(function(){
this.selectedSpell = new ReactiveVar();
this.searchTerm = new ReactiveVar();
this.categoriesOpen = new ReactiveVar([]);
this.readyDict = new ReactiveDict();
this.searchReady = new ReactiveVar();
librarySubs.subscribe("standardLibraries");
this.autorun(() => {
// Subscribe to all open categories
_.each(this.categoriesOpen.get(), (key) => {
var handle = librarySubs.subscribe("standardLibrarySpells", key);
this.autorun(() => {
this.readyDict.set(key, handle.ready());
});
});
});
this.autorun(() => {
// If we are searching, subscibe to all categories
if (this.searchTerm.get()){
let handles = _.map(categories, category =>
librarySubs.subscribe("standardLibrarySpells", category.key)
);
// Ready when all handles are ready
this.autorun(() => {
this.searchReady.set(_.every(handles, h => h.ready()));
});
}
});
});
Template.spellLibraryDialog.helpers({
ready(key){
return Template.instance().readyDict.get(key);
},
categories(){
return categories;
},
spellsInCategory(categoryKey){
return LibrarySpells.find({
library: "SRDLibraryGA3XWsd",
level: categoryKey,
}, {
sort: {name: 1},
});
},
isSelected(spell){
const selected = Template.instance().selectedSpell.get();
return selected && selected._id === spell._id;
},
isOpen(key){
const cats = Template.instance().categoriesOpen.get();
return _.contains(cats, key);
},
searchTerm(){
return Template.instance().searchTerm.get();
},
searchReady(){
return Template.instance().searchReady.get();
},
searchSpells(){
const searchTerm = Template.instance().searchTerm.get();
if (!searchTerm) return;
return LibrarySpells.find({
library: "SRDLibraryGA3XWsd",
name: {
$regex: new RegExp(".*" + searchTerm + ".*", "gi")
},
});
},
});
Template.spellLibraryDialog.events({
"click .cancelButton": function(event, template){
popDialogStack();
},
"click .okButton": function(event, template){
popDialogStack(template.selectedSpell.get());
},
"click .library-spell": function(event, template){
template.selectedSpell.set(this.spell);
},
"click #backButton": function(event, template){
popDialogStack();
},
"click .category-header": function(event, template){
let cats = template.categoriesOpen.get();
const key = this.key;
// Toggle whether this key is in the array or not
if (_.contains(cats, key)){
cats = _.without(cats, key);
} else {
cats.push(key);
}
template.categoriesOpen.set(cats);
},
"input .search-input, change .search-input": function(event, template){
const value = event.currentTarget.value;
template.searchTerm.set(value);
},
});

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,76 @@ pushDialogStack = function({template, data, element, returnElement, callback}){
returnElement, returnElement,
callback, callback,
}); });
updateHistory();
}; };
popDialogStack = function(result){ popDialogStack = function(result){
if (history && history.state && history.state.openDialogs){
history.back();
} else {
popDialogStackAction();
}
}
window.onpopstate = function(event){
let state = event.state;
let numDialogs = dialogs._array.length;
if (_.isFinite(state.openDialogs) && numDialogs > state.openDialogs){
popDialogStackAction();
}
}
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";
@@ -174,7 +236,7 @@ const dialogCloseAnimation = ({element, returnElement, dialog, callback}) => {
const stackCompensation = dialogs._array.length ? 16 : 0; const stackCompensation = dialogs._array.length ? 16 : 0;
// Insert clone before its progenitor so it can inherit css correctly // Insert clone before its progenitor so it can inherit css correctly
element.parentNode.insertBefore(clone, element); element.parentNode && element.parentNode.insertBefore(clone, element);
// Polymer messes up fixed positioning, measure and compensate // Polymer messes up fixed positioning, measure and compensate
startingRect = clone.getBoundingClientRect(); startingRect = clone.getBoundingClientRect();

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,
};

View File

@@ -14,3 +14,12 @@ Meteor.publish("standardLibraryItems", function(categoryKey){
sort: {name: 1}, sort: {name: 1},
}); });
}); });
Meteor.publish("standardLibrarySpells", function(level){
return LibrarySpells.find({
library: {$in: standardLibraryIds},
level,
}, {
sort: {name: 1},
});
});