Compare commits

..

8 Commits
1.1.1 ... 1.2.0

Author SHA1 Message Date
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
11 changed files with 5088 additions and 4 deletions

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

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

@@ -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,48 @@ 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.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

@@ -174,7 +174,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

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