Compare commits

..

29 Commits
1.1.0 ... 1.2.6

Author SHA1 Message Date
Stefan Zermatten
8f30cee4d3 Hotfixed column layout with translateZ(0) hack 2017-06-07 09:46:38 +02:00
Stefan Zermatten
d7f7eb2e6a Fixed some spells in the library 2017-06-07 09:35:59 +02:00
Stefan Zermatten
a59cf1162f added return value to remove old soft removed docs 2017-05-29 11:58:19 +02:00
Stefan Zermatten
7bc80da99e Moved cron setup to startup 2017-05-29 11:35:30 +02:00
Stefan Zermatten
2ddc520bb6 Added cron job to remove old documents 2017-05-29 11:16:46 +02:00
Stefan Zermatten
d92bb0f4d4 Replace tap event with click event for mini-FABs
fixes #68, probably
2017-05-16 10:36:40 +02:00
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
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
Stefan Zermatten
5ee1f0a169 Merge branch 'feature-library' 2017-05-04 14:23:19 +02:00
Stefan Zermatten
b0652b0102 Added SRD tools
#47
2017-05-04 14:22:55 +02:00
Stefan Zermatten
47b68c777d Improved item library dialog UI 2017-05-04 14:19:47 +02:00
Stefan Zermatten
16feaaa6ca Fixed jshint breaking on es6 2017-05-04 14:19:11 +02:00
Stefan Zermatten
8a38c167c0 Removed padding from drawer 2017-05-04 09:41:09 +02:00
Stefan Zermatten
1835d968f3 Merge branch 'bugfix-smallbugs' 2017-04-25 17:16:05 +02:00
Stefan Zermatten
2a9d170647 Added spacing between values and units in the inventory
closes #44 closes #60
2017-04-25 17:12:34 +02:00
Stefan Zermatten
b4649d8c87 Fixed spacing between "weight carried" and "x lbs" 2017-04-25 16:59:54 +02:00
Stefan Zermatten
e50bcca7ce Made feature uses a text field again
Fixes #59
2017-04-25 16:23:16 +02:00
43 changed files with 5902 additions and 171 deletions

View File

@@ -1,3 +1,4 @@
{
"undef": false
}
"undef": false,
"esversion": 6
}

View File

@@ -1,13 +1,13 @@
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
---------------
`git clone https://github.com/ThaumRystra/RPG-Docs RPG-Docs`
`cd RPG-Docs`
`git clone https://github.com/ThaumRystra/DiceCloud1 dicecloud`
`cd dicecloud`
`cd rpg-docs`
`bower install`
`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"
LibraryItems.insert(item)
});
_.each(spells, (spell) => {
spell.library = "SRDLibraryGA3XWsd"
LibrarySpells.insert(spell)
});

247
dataSources/srd/tools.json Normal file
View File

@@ -0,0 +1,247 @@
[
{
"name": "Alchemists supplies",
"plural": "Alchemists supplies",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 50,
"weight": 8
},
{
"name": "Brewers supplies",
"plural": "Brewers supplies",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 20,
"weight": 9
},
{
"name": "Calligraphers supplies",
"plural": "Calligraphers supplies",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 10,
"weight": 5
},
{
"name": "Carpenters tools",
"plural": "Carpenters tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 8,
"weight": 6
},
{
"name": "Cartographers tools",
"plural": "Cartographers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 15,
"weight": 6
},
{
"name": "Cobblers tools",
"plural": "Cobblers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 5,
"weight": 5
},
{
"name": "Cooks utensils",
"plural": "Cooks utensils",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 1,
"weight": 8
},
{
"name": "Glassblowers tools",
"plural": "Glassblowers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 30,
"weight": 5
},
{
"name": "Jewelers tools",
"plural": "Jewelers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 25,
"weight": 2
},
{
"name": "Leatherworkers tools",
"plural": "Leatherworkers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 5,
"weight": 5
},
{
"name": "Masons tools",
"plural": "Masons tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 10,
"weight": 8
},
{
"name": "Painters supplies",
"plural": "Painters supplies",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 10,
"weight": 5
},
{
"name": "Potters tools",
"plural": "Potters tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 10,
"weight": 3
},
{
"name": "Smiths tools",
"plural": "Smiths tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 20,
"weight": 8
},
{
"name": "Tinkers tools",
"plural": "Tinkers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 50,
"weight": 10
},
{
"name": "Weavers tools",
"plural": "Weavers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 1,
"weight": 5
},
{
"name": "Woodcarvers tools",
"plural": "Woodcarvers tools",
"description": "These special tools include the items needed to pursue a craft or trade. The table shows examples of the most common types of tools, each providing items related to a single craft. Proficiency with a set of artisans tools lets you add your proficiency bonus to any ability checks you make using the tools in your craft. Each type of artisans tools requires a separate proficiency.",
"value": 1,
"weight": 5
},
{
"name": "Dice set",
"plural": "Dice sets",
"description": "This item encompasses a wide range of game pieces, including dice and decks of cards (for games such as Three-Dragon Ante). A few common examples appear on the Tools table, but other kinds of gaming sets exist. If you are proficient with a gaming set, you can add your proficiency bonus to ability checks you make to play a game with that set. Each type of gaming set requires a separate proficiency.",
"value": 0.1,
"weight": 0
},
{
"name": "Playing card set",
"plural": "Playing card sets",
"description": "This item encompasses a wide range of game pieces, including dice and decks of cards (for games such as Three-Dragon Ante). A few common examples appear on the Tools table, but other kinds of gaming sets exist. If you are proficient with a gaming set, you can add your proficiency bonus to ability checks you make to play a game with that set. Each type of gaming set requires a separate proficiency.",
"value": 0.5,
"weight": 0
},
{
"name": "Bagpipes",
"plural": "Bagpipes",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 30,
"weight": 6
},
{
"name": "Drum",
"plural": "Drums",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 6,
"weight": 3
},
{
"name": "Dulcimer",
"plural": "Dulcimers",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 25,
"weight": 10
},
{
"name": "Flute",
"plural": "Flutes",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 2,
"weight": 1
},
{
"name": "Lute",
"plural": "Lutes",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 35,
"weight": 2
},
{
"name": "Lyre",
"plural": "Lyres",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 30,
"weight": 2
},
{
"name": "Horn",
"plural": "Horns",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 3,
"weight": 2
},
{
"name": "Pan flute",
"plural": "Pan flutes",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 12,
"weight": 2
},
{
"name": "Shawm",
"plural": "Shawms",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 2,
"weight": 1
},
{
"name": "Viol",
"plural": "Viols",
"description": "If you have proficiency with a given musical instrument, you can add your proficiency bonus to any ability checks you make to play music with the instrument. A bard can use a musical instrument as a spellcasting focus.",
"value": 30,
"weight": 1
},
{
"name": "Disguise Kit",
"plural": "Disguise Kits",
"description": "This pouch of cosmetics, hair dye, and small props lets you create disguises that change your physical appearance. Proficiency with this kit lets you add your proficiency bonus to any ability checks you make to create a visual disguise.",
"value": 25,
"weight": 3
},
{
"name": "Forgery Kit",
"plural": "Forgery Kits",
"description": "This small box contains a variety of papers and parchments, pens and inks, seals and sealing wax, gold and silver leaf, and other supplies necessary to create convincing forgeries of physical documents. Proficiency with this kit lets you add your proficiency bonus to any ability checks you make to create a physical forgery of a document.",
"value": 15,
"weight": 5
},
{
"name": "Herbalism Kit",
"plural": "Herbalism Kits",
"description": "This kit contains a variety of instruments such as clippers, mortar and pestle, and pouches and vials used by herbalists to create remedies and potions. Proficiency with this kit lets you add your proficiency bonus to any ability checks you make to identify or apply herbs. Also, proficiency with this kit is required to create antitoxin and potions of healing.",
"value": 5,
"weight": 3
},
{
"name": "Navigators tools",
"plural": "Navigators tools",
"description": "This set of instruments is used for navigation at sea. Proficiency with navigators tools lets you chart a ships course and follow navigation charts. In addition, these tools allow you to add your proficiency bonus to any ability check you make to avoid getting lost at sea.",
"value": 25,
"weight": 2
},
{
"name": "Thieves tools",
"plural": "Thieves tools",
"description": "This set of tools includes a small file, a set of lock picks, a small mirror mounted on a metal handle, a set of narrow-bladed scissors, and a pair of pliers. Proficiency with these tools lets you add your proficiency bonus to any ability checks you make to disarm traps or open locks.",
"value": 25,
"weight": 1
},
{
"name": "Poisoner's Kit",
"plural": "Poisoner's Kits",
"description": "A poisoners kit includes the vials, chemicals, and other equipment necessary for the creation of poisons. Proficiency with this kit lets you add your proficiency bonus to any ability checks you make to craft or use poisons.",
"value": 50,
"weight": 2
}
]

View File

@@ -46,3 +46,5 @@ templates:array
ecmascript@0.6.1
es5-shim@4.6.15
differential:vulcanize
reactive-dict
percolate:synced-cron

View File

@@ -87,6 +87,7 @@ oauth2@1.1.11
observe-sequence@1.0.14
ordered-dict@1.0.9
percolate:migrations@0.9.8
percolate:synced-cron@1.3.2
promise@0.8.8
raix:eventemitter@0.1.3
random@1.0.10

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

@@ -10,13 +10,13 @@ Template.registerHelper("valueString", function(value) {
var resultArray = [];
if (gp > 0) {
resultArray.push(gp + "gp");
resultArray.push(gp + " gp");
}
if (sp > 0) {
resultArray.push(sp + "sp");
resultArray.push(sp + " sp");
}
if (cp > 0) {
resultArray.push(cp + "cp");
resultArray.push(cp + " cp");
}
//build string with correct spacing
@@ -36,18 +36,18 @@ Template.registerHelper("longValueString", function(value) {
//sp
var gp = Math.floor(value);
if (gp > 0) {
resultArray.push(gp + "gp");
resultArray.push(gp + " gp");
}
//sp
var sp = Math.floor(10 * (value % 1));
if (sp > 0 || resultArray.length) {
resultArray.push(sp + "sp");
resultArray.push(sp + " sp");
}
//cp
var cp = 10 * ((value * 10) % 1);
cp = Math.round(cp * 1000) / 1000;
if (cp > 0 || resultArray.length) {
resultArray.push(cp + "cp");
resultArray.push(cp + " cp");
}
//build string with correct spacing

View File

@@ -4,6 +4,7 @@
column-gap: 0px;
column-width: 304px;
padding: 4px;
transform: translateZ(0);
}
.column-container.thin-columns {

View File

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

View File

@@ -97,7 +97,19 @@ var skillOperations = [
{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({
effect: function(){
return Effects.findOne(this.id);
},
statGroups: function(){
return statGroupNames;
},
@@ -115,46 +127,77 @@ Template.effectEdit.helpers({
return attributeOperations;
}
},
effectValueTemplate: function(){
//resistance/vulnerability template
showMultiplierOperations: function(){
var stat = statsDict[this.stat];
return stat && stat.group === "Weakness/Resistance"
},
showEffectValueInput: function(){
var stat = statsDict[this.stat];
var group = stat && stat.group;
if (group === "Weakness/Resistance") return "multiplierEffectValue";
if (
group === "Weakness/Resistance"
) return false;
var op = this.operation;
if (!op) return null;
//operations that don't need templates
if (op === "advantage" || op === "disadvantage" || op === "fail") return null;
//default template
return "regularEffectValue";
if (
!op ||
op === "advantage" ||
op === "disadvantage" ||
op === "fail"
) return false;
return true;
},
});
Template.regularEffectValue.helpers({
effectValue: function(){
return this.calculation || this.value;
}
},
});
Template.effectEdit.events({
"click .deleteEffect": function(event){
Effects.softRemoveNode(this._id);
GlobalUI.deletedToast(this._id, "Effects", "Effect");
"click #deleteButton": function(event, instance){
Effects.softRemoveNode(instance.data.id);
GlobalUI.deletedToast(instance.data.id, "Effects", "Effect");
popDialogStack();
},
"iron-select .statDropDown": function(event){
"iron-select .statMenu": function(event){
var detail = event.originalEvent.detail;
var statName = detail.item.getAttribute("name");
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 opName = detail.item.getAttribute("name");
if (opName == this.operation) return;
Effects.update(this._id, {$set: {operation: opName}});
},
"iron-select .damageMultiplierDropDown": function(event){
"iron-select .multiplierMenu": function(event){
var detail = event.originalEvent.detail;
var value = +detail.item.getAttribute("name");
if (value == this.value) return;
@@ -164,15 +207,25 @@ Template.effectEdit.events({
operation: "mul",
}});
},
"change .effectValueInput": function(event){
"change .effectValueInput, input .effectValueInput":
_.debounce(function(event){
var value = event.currentTarget.value;
var numValue = +value;
var numValue = value === "" ? NaN : +value;
if (_.isFinite(numValue)){
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)){
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">
<tr>
<td>{{statName}}</td>
<td>{{operationName}}{{statValue}}</td>
</tr>
<td>{{statName}}</td>
<td>{{operationName}}{{statValue}}</td>
</template>

View File

@@ -1,4 +1,3 @@
//TODO add dexterity armor
var stats = {
"strength":{"name":"Strength"},
"dexterity":{"name":"Dexterity"},
@@ -131,8 +130,10 @@ Template.effectView.helpers({
return stats[this.stat] && stats[this.stat].name || "No Stat";
},
operationName: function(){
if (this.operation === "proficiency" ||
this.operation === "conditional") return null;
if (
this.operation === "proficiency" ||
this.operation === "conditional"
) return null;
if (stats[this.stat] && stats[this.stat].group === "Weakness/Resistance")
return null;
if (this.operation === "add" && evaluateEffect(this.charId, this) < 0)
@@ -141,9 +142,11 @@ Template.effectView.helpers({
operations[this.operation].name || "No Operation";
},
statValue: function(){
if (this.operation === "advantage" ||
this.operation === "disadvantage" ||
this.operation === "fail"){
if (
this.operation === "advantage" ||
this.operation === "disadvantage" ||
this.operation === "fail"
){
return null;
}
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-->
<template name="effectsEditList">
{{#if effects.count}}
<div id="effects">
{{#if effects.length}}
<div class="effectsEditList">
<div class="paper-font-title">Effects</div>
{{#each effects}}
{{>effectEdit}}
{{/each}}
<table class="wideTable" style="width: 100%;">
{{#each effects}}
<tr class="effect" data-id={{_id}}>
{{>effectView}}
<td>
<paper-icon-button class="edit-effect" icon="create">
</paper-icon-button>
</td>
</tr>
{{/each}}
</table>
</div>
{{/if}}
<paper-button id="addEffectButton" class="red-button" raised>Add Effect</paper-button>

View File

@@ -8,17 +8,17 @@ Template.effectsEditList.helpers({
if (this.parentGroup){
selector["parent.group"] = this.parentGroup;
}
var cursor = Effects.find(selector);
return cursor;
var effects = Effects.find(selector).fetch();
return _.sortBy(effects, effect => statOrder[effect.stat] || 999);
}
});
Template.effectsEditList.events({
"tap #addEffectButton": function(){
"tap #addEffectButton": function(event, instance){
if (!_.isBoolean(this.enabled)) {
this.enabled = true;
}
Effects.insert({
const effectId = Effects.insert({
name: this.name,
charId: this.charId,
parent: {
@@ -29,5 +29,18 @@ Template.effectsEditList.events({
operation: "add",
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-->
<template name="effectsViewList">
{{#if effects.count}}
{{#if effects.length}}
<div class="effects">
<div class="spaceAfter paper-font-title">Effects</div>
<table class="wideTable">
{{#each effects}}
{{>effectView}}
<tr>{{>effectView}}</tr>
{{/each}}
</table>
</div>

View File

@@ -2,11 +2,14 @@ Template.effectsViewList.helpers({
effects: function(){
var selector = {
"parent.id": this.parentId,
"charId": this.charId
"charId": this.charId,
};
if (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

@@ -60,8 +60,8 @@
Limit uses
</paper-toggle-button>
{{#if usesSet}}
<paper-input id="usesInput" type="number" allowed-pattern="[0-9]" class="flex"
label="Uses" value={{uses}} style="flex-basis: 100px; max-width: 200px">
<paper-input id="usesInput" class="flex" label="Uses" value={{uses}} style="flex-basis: 100px; max-width: 200px">
{{> formulaSuffix}}
</paper-input>
{{else}}
<div class="flex" style="flex-basis: 100px; max-width: 200px"></div>

View File

@@ -28,9 +28,9 @@
<template name="containerView">
<div class="layout horizontal wrap center justified">
<table class="summaryTable fullwidth">
<tr><td>Container</td><td>{{round weight}}lbs</td><td>{{longValueString value}}</td></tr>
<tr><td>Contents</td><td>{{round contentsWeight}}lbs</td><td>{{longValueString contentsValue}}</td></tr>
<tr class="paper-font-body2"><td>Total</td><td>{{round totalWeight}}lbs</td><td>{{longValueString totalValue}}</td></tr>
<tr><td>Container</td><td>{{round weight}} lbs</td><td>{{longValueString value}}</td></tr>
<tr><td>Contents</td><td>{{round contentsWeight}} lbs</td><td>{{longValueString contentsValue}}</td></tr>
<tr class="paper-font-body2"><td>Total</td><td>{{round totalWeight}} lbs</td><td>{{longValueString totalValue}}</td></tr>
</table>
</div>
{{#if description}}

View File

@@ -18,11 +18,11 @@
<div>
<paper-material class="card">
<div class="top green white-text weightCarried layout horizontal center">
<div class="paper-font-subhead" flex>
<div class="paper-font-subhead flex">
Weight Carried
</div>
<div>
{{round weightCarried}}lbs
{{round weightCarried}} lbs
</div>
</div>
<div class="bottom green" style="padding: 0;">
@@ -57,7 +57,7 @@
{{valueString equipmentValue}}
</div>
<div class="paper-font-caption">
{{round equipmentWeight}}lbs
{{round equipmentWeight}} lbs
</div>
</div>
<div flex class="bottom list">
@@ -87,7 +87,7 @@
{{valueString carriedValue}}
</div>
<div class="paper-font-caption">
{{round carriedWeight}}lbs
{{round carriedWeight}} lbs
</div>
</div>
<div flex class="bottom list">
@@ -108,7 +108,7 @@
{{valueString totalValue}}
</div>
<div class="paper-font-caption" style="margin-right: 8px">
{{round totalWeight}}lbs
{{round totalWeight}} lbs
</div>
<div>
<paper-checkbox class="carriedCheckbox"

View File

@@ -10,7 +10,7 @@
<template name="itemDetails">
<div class="paper-font-headline layout horizontal wrap center justified">
{{#if weight}}<div class="sideMargin">{{round totalWeight}}lbs</div>{{/if}}
{{#if weight}}<div class="sideMargin">{{round totalWeight}} lbs</div>{{/if}}
{{#if value}}<div>{{valueString totalValue}}</div>{{/if}}
</div>
<div class="paper-font-caption layout horizontal wrap">

View File

@@ -2,6 +2,22 @@
background-color: #e4e4e4;
}
.item-library-dialog .paper-font-subhead {
color: rgba(0,0,0,0.54);
.item-library-dialog .category-header {
font-size: 16px;
}
.item-library-dialog .category-header iron-icon {
transition: transform 0.3s ease;
}
.item-library-dialog .category-header iron-icon.open {
transform: rotate(90deg);
}
.item-library-dialog table {
border-collapse: collapse;
}
.item-library-dialog .library-item td {
position: relative;
}

View File

@@ -10,32 +10,46 @@
</paper-input>
</app-toolbar>
<div class="flex scroll-y">
{{#if ready}}
<div class="items" style="padding:8px">
{{#if searchTerm}}
{{#if searchItems.count}}
{{#each item in searchItems}}
{{>libraryItem item=item selected=(isSelected item)}}
{{/each}}
{{else}}
<div class="items" style="padding:8px">
{{#if searchTerm}}
{{#if searchItems.count}}
<table style="width: 100%">
<tbody>
{{#each item in searchItems}}
{{>libraryItem item=item selected=(isSelected item)}}
{{/each}}
</tbody>
</table>
{{else}}{{#if searchReady}}
No items match "{{searchTerm}}"
{{/if}}
{{else}}
{{#each category in categories}}
<div class="paper-font-subhead">
{{category.name}}
</div>
{{#each item in (itemsInCategory category.key)}}
{{>libraryItem item=item selected=(isSelected item)}}
{{/each}}
{{/each}}
{{/if}}
</div>
{{else}}
<div class="fit layout vertical center center-justified">
<paper-spinner active></paper-spinner>
</div>
{{/if}}
{{/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 item in (itemsInCategory key)}}
{{>libraryItem item=item selected=(isSelected item)}}
{{/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>
@@ -45,10 +59,18 @@
</template>
<template name="libraryItem">
<div class="item library-item layout horizontal center {{#if selected}}selected{{/if}}">
<paper-ripple></paper-ripple>
<div class="itemName flex">
<tr class="item library-item {{#if selected}}selected{{/if}}">
<td class="itemName">
{{item.name}}
</div>
</div>
<paper-ripple></paper-ripple>
</td>
<td>
{{item.weight}} lb.
<paper-ripple></paper-ripple>
</td>
<td>
{{valueString item.value}}
<paper-ripple></paper-ripple>
</td>
</tr>
</template>

View File

@@ -1,41 +1,74 @@
const librarySubs = new SubsManager();
const categories = [
{name: "Weapons", key: "weapons"},
{name: "Armor", key: "armor"},
{name: "Adventuring Gear", key: "adventuringGear"},
{name: "Tools", key: "tools"},
];
Template.itemLibraryDialog.onCreated(function(){
this.selectedItem = new ReactiveVar();
this.searchTerm = new ReactiveVar();
this.ready = new ReactiveVar();
this.categoriesOpen = new ReactiveVar([]);
this.readyDict = new ReactiveDict();
this.searchReady = new ReactiveVar();
librarySubs.subscribe("standardLibraries");
this.autorun(() => {
var handle = librarySubs.subscribe("standardLibraries");
this.ready.set(handle.ready());
// Subscribe to all open categories
_.each(this.categoriesOpen.get(), (key) => {
var handle = librarySubs.subscribe("standardLibraryItems", 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("standardLibraryItems", category.key)
);
// Ready when all handles are ready
this.autorun(() => {
this.searchReady.set(_.every(handles, h => h.ready()));
});
}
});
});
Template.itemLibraryDialog.helpers({
ready(){
return Template.instance().ready.get();
ready(key){
return Template.instance().readyDict.get(key);
},
categories(){
return [
{name: "Weapons", key: "weapons"},
{name: "Armor", key: "armor"},
{name: "Adventuring Gear", key: "adventuringGear"},
];
return categories;
},
itemsInCategory(categoryKey){
return LibraryItems.find({
library: "SRDLibraryGA3XWsd",
"settings.category": categoryKey,
}, {
sort: {name: 1},
});
},
isSelected(item){
const selected = Template.instance().selectedItem.get();
return selected && selected._id === item._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();
},
searchItems(){
const searchTerm = Template.instance().searchTerm.get();
if (!searchTerm) return;
return LibraryItems.find({
library: "SRDLibraryGA3XWsd",
name: {
@@ -58,6 +91,17 @@ Template.itemLibraryDialog.events({
"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

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

View File

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

View File

@@ -211,8 +211,17 @@ Template.spells.events({
});
},
"click .addSpell": function(event, instance){
var charId = this.charId;
var listId = SpellLists.findOne({charId: this._id})._id;
var charId = this._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({
name: "New Spell",
charId: this._id,
@@ -229,6 +238,49 @@ Template.spells.events({
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){
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

@@ -7,10 +7,6 @@
display: initial !important;
}
#navPanel {
padding: 16px;
}
#navPanel paper-icon-item {
background: white;
cursor: pointer;

View File

@@ -27,7 +27,7 @@
{{/if}}
{{/if}}
</app-toolbar>
<div class="form flex scroll-y">
<div class="form flex scroll-y" style="position: relative;">
{{#unless editing}}
{{> UI.contentBlock}}
{{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,
callback,
});
updateHistory();
};
var currentResult;
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();
updateHistory();
if (!dialog) return;
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({
dialogStackClass(){
if (!dialogs.get().length) return "hide";
@@ -174,7 +240,7 @@ const dialogCloseAnimation = ({element, returnElement, dialog, callback}) => {
const stackCompensation = dialogs._array.length ? 16 : 0;
// 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
startingRect = clone.getBoundingClientRect();

View File

@@ -25,10 +25,10 @@ Template.fabMenu.helpers({
});
Template.fabMenu.events({
"tap .expand-menu": function(event, instance) {
"click .expand-menu": function(event, instance) {
instance.active.set(!instance.active.get());
},
"tap .mini-holder paper-fab": function(event, instance) {
"click .mini-holder paper-fab": function(event, instance) {
instance.active.set(false);
},
});

View File

@@ -8,6 +8,7 @@
"/components/app-layout/app-scroll-effects/effects/parallax-background.html",
"/components/app-layout/app-scroll-effects/effects/resize-title.html",
"/components/iron-collapse/iron-collapse.html",
"/components/iron-icon/iron-icon.html",
"/components/iron-icons/av-icons.html",
"/components/iron-icons/editor-icons.html",

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

@@ -0,0 +1,45 @@
Meteor.startup(() => {
const collections = [
Attacks, Buffs, Classes, Effects, Experiences,
Features, Notes, Proficiencies, SpellLists, Spells,
Containers, Items,
];
/**
* Deletes all soft removed documents that were removed more than 30 minutes ago
* and were not restored
* @return {Number} Number of documents removed
*/
const deleteOldSoftRemovedDocs = function(){
let numRemoved = 0;
const now = new Date();
const thirtyMinutesAgo = new Date(now.getTime() - 30*60000);
_.each(collections, (collection) => {
numRemoved += collection.remove({
removed: true,
removedAt: {$lt: thirtyMinutesAgo} // dates *before* 30 minutes ago
});
});
return numRemoved;
};
SyncedCron.add({
name: "Delete all soft removed items that haven't been restored",
schedule: function(parser) {
return parser.text('every 6 hours');
},
job: function() {
deleteOldSoftRemovedDocs();
}
});
// Add a method to manually trigger removal
Meteor.methods({
deleteOldSoftRemovedDocs() {
const user = Meteor.users.findOne(this.userId);
if (user && _.contains(user.roles, "admin")){
return deleteOldSoftRemovedDocs();
}
},
});
});

View File

@@ -1,9 +1,25 @@
const standardLibraryIds = [
"SRDLibraryGA3XWsd",
];
Meteor.publish("standardLibraries", function(){
const standardLibraryIds = [
"SRDLibraryGA3XWsd",
];
return [
LibraryItems.find({library: {$in: standardLibraryIds}}),
Libraries.find({_id: {$in: standardLibraryIds}}),
];
return Libraries.find({_id: {$in: standardLibraryIds}});
});
Meteor.publish("standardLibraryItems", function(categoryKey){
return LibraryItems.find({
library: {$in: standardLibraryIds},
"settings.category": categoryKey,
}, {
sort: {name: 1},
});
});
Meteor.publish("standardLibrarySpells", function(level){
return LibrarySpells.find({
library: {$in: standardLibraryIds},
level,
}, {
sort: {name: 1},
});
});