Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
398f8a8a2a | ||
|
|
812a1784b2 | ||
|
|
8fa9cd0148 | ||
|
|
0e0662cc9a | ||
|
|
ad4e3f5b20 | ||
|
|
4cd058e1fe | ||
|
|
8f30cee4d3 | ||
|
|
d7f7eb2e6a | ||
|
|
a59cf1162f | ||
|
|
7bc80da99e | ||
|
|
2ddc520bb6 | ||
|
|
d92bb0f4d4 | ||
|
|
abcfe57add | ||
|
|
cdae75beef | ||
|
|
e7bcc2224c | ||
|
|
b591c66dd5 | ||
|
|
402bc0e5ed | ||
|
|
ac772531ee | ||
|
|
9a3edb07e4 | ||
|
|
fea45d2398 | ||
|
|
18aa1b2040 | ||
|
|
9afe38d043 | ||
|
|
5ddbecf97e | ||
|
|
7b573ca86e | ||
|
|
a9256fed05 | ||
|
|
8ec10e7a6a |
@@ -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
4739
dataSources/srd/spells.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,3 +10,8 @@ _.each(items, (item) => {
|
||||
item.library = "SRDLibraryGA3XWsd"
|
||||
LibraryItems.insert(item)
|
||||
});
|
||||
|
||||
_.each(spells, (spell) => {
|
||||
spell.library = "SRDLibraryGA3XWsd"
|
||||
LibrarySpells.insert(spell)
|
||||
});
|
||||
|
||||
@@ -47,3 +47,4 @@ ecmascript@0.6.1
|
||||
es5-shim@4.6.15
|
||||
differential:vulcanize
|
||||
reactive-dict
|
||||
percolate:synced-cron
|
||||
|
||||
@@ -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
|
||||
|
||||
66
rpg-docs/Model/Library/LibrarySpells.js
Normal file
66
rpg-docs/Model/Library/LibrarySpells.js
Normal 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"],
|
||||
});
|
||||
@@ -4,6 +4,7 @@
|
||||
column-gap: 0px;
|
||||
column-width: 304px;
|
||||
padding: 4px;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.column-container.thin-columns {
|
||||
@@ -22,6 +23,7 @@
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 2px;
|
||||
position: initial;
|
||||
}
|
||||
|
||||
.card .top {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
body .paper-font-display4, body .paper-font-display3, body .paper-font-title, body .paper-font-caption{
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.white-text {
|
||||
color: #dedede;
|
||||
color: rgba(255,255,255,0.87);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<template name="effectView">
|
||||
<tr>
|
||||
<td>{{statName}}</td>
|
||||
<td>{{operationName}}{{statValue}}</td>
|
||||
</tr>
|
||||
<td>{{statName}}</td>
|
||||
<td>{{operationName}}{{statValue}}</td>
|
||||
</template>
|
||||
|
||||
@@ -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"){
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.effectsEditList .effect {
|
||||
background: white;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -78,6 +78,20 @@
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.spell.item > div > div {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.spell.item > div {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.spell.item iron-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.spellLevel {
|
||||
-webkit-backface-visibility: hidden;
|
||||
-webkit-transform: translateX(0);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -28,8 +28,8 @@
|
||||
{{/unless}}
|
||||
</div>
|
||||
<paper-diff-slider class="tempHitPointSlider flex"
|
||||
value={{left}}
|
||||
max={{maximum}}
|
||||
value={{left}}
|
||||
editable pin
|
||||
></paper-diff-slider>
|
||||
</div>
|
||||
|
||||
@@ -21,10 +21,15 @@
|
||||
max-width: 300px;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.intro .section .columns {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.intro paper-button {
|
||||
min-width: 200px;
|
||||
flex-basis: 200px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
</div>
|
||||
<div class="section white-text" style="background: #282828">
|
||||
<div class="columns layout horizontal around-justified wrap">
|
||||
<div>
|
||||
<div class="layout vertical center">
|
||||
<div class="paper-font-headline">
|
||||
Guide
|
||||
</div>
|
||||
@@ -78,7 +78,7 @@
|
||||
</paper-button>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<div class="layout vertical center">
|
||||
<div class="paper-font-headline">
|
||||
Discuss
|
||||
</div>
|
||||
@@ -91,7 +91,7 @@
|
||||
</paper-button>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<div class="layout vertical center">
|
||||
<div class="paper-font-headline">
|
||||
Get involved
|
||||
</div>
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
Template.baseEditDialog.events({
|
||||
"tap #backButton": function(){
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
71
rpg-docs/lib/constants/statOrder.js
Normal file
71
rpg-docs/lib/constants/statOrder.js
Normal 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,
|
||||
};
|
||||
45
rpg-docs/server/lib/cron/deleteRemovedDocuments.js
Normal file
45
rpg-docs/server/lib/cron/deleteRemovedDocuments.js
Normal 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();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -14,3 +14,12 @@ Meteor.publish("standardLibraryItems", function(categoryKey){
|
||||
sort: {name: 1},
|
||||
});
|
||||
});
|
||||
|
||||
Meteor.publish("standardLibrarySpells", function(level){
|
||||
return LibrarySpells.find({
|
||||
library: {$in: standardLibraryIds},
|
||||
level,
|
||||
}, {
|
||||
sort: {name: 1},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user