Cobbled together some semblance of an item library UI
This commit is contained in:
@@ -1,5 +1,29 @@
|
||||
<template name="itemLibrary">
|
||||
{{#each items}}
|
||||
{{> libraryItem}}
|
||||
{{#each libraries}}
|
||||
<div class="paper-font-subhead library-header layout horizontal center" data-id={{_id}} style="height: 40px;">
|
||||
<iron-icon icon="chevron-right" class="{{#if isOpen _id}}open{{/if}}">
|
||||
</iron-icon>
|
||||
<div class="flex">{{name}}</div>
|
||||
{{#if isOpen _id}}
|
||||
<div class="relative">
|
||||
<paper-icon-button icon="create" class="editLibrary"></paper-icon-button>
|
||||
{{#simpleTooltip}}Edit Library{{/simpleTooltip}}
|
||||
</div>
|
||||
<div class="relative">
|
||||
<paper-icon-button icon="add" class="addItem"></paper-icon-button>
|
||||
{{#simpleTooltip}}Add Item{{/simpleTooltip}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<iron-collapse opened={{isOpen _id}}>
|
||||
{{#each libraryItems}}
|
||||
<paper-item class="short item-name" data-id={{_id}}>
|
||||
{{name}}
|
||||
</paper-item>
|
||||
{{/each}}
|
||||
{{#unless ready _id}}
|
||||
<paper-spinner active></paper-spinner>
|
||||
{{/unless}}
|
||||
</iron-collapse>
|
||||
{{/each}}
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,94 @@
|
||||
const librarySubs = new SubsManager();
|
||||
|
||||
Template.itemLibrary.onCreated(function(){
|
||||
this.selectedTab = new ReactiveVar("0");
|
||||
this.librariesOpen = new ReactiveVar([]);
|
||||
this.readyDict = new ReactiveDict();
|
||||
this.autorun(() => {
|
||||
// Subscribe to all open libraries
|
||||
_.each(this.librariesOpen.get(), (libraryId) => {
|
||||
var handle = librarySubs.subscribe("libraryItems", libraryId);
|
||||
this.autorun(() => {
|
||||
this.readyDict.set(libraryId, handle.ready());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Template.itemLibrary.helpers({
|
||||
items(){
|
||||
return Items.find({charId: {$in: [
|
||||
"SRDLibrary",
|
||||
]}});
|
||||
selectedTab(){
|
||||
return Template.instance().selectedTab.get();
|
||||
},
|
||||
libraries(){
|
||||
let userId = Meteor.userId();
|
||||
return Libraries.find({
|
||||
$or: [
|
||||
{readers: userId},
|
||||
{writers: userId},
|
||||
{owner: userId},
|
||||
],
|
||||
});
|
||||
},
|
||||
libraryItems(){
|
||||
return LibraryItems.find({
|
||||
library: this._id
|
||||
},{
|
||||
sort: {name: 1}
|
||||
});
|
||||
},
|
||||
ready(libraryId){
|
||||
return Template.instance().readyDict.get(libraryId);
|
||||
},
|
||||
isOpen(libraryId){
|
||||
const librariesOpen = Template.instance().librariesOpen.get();
|
||||
return _.contains(librariesOpen, libraryId);
|
||||
},
|
||||
});
|
||||
|
||||
Template.itemLibrary.events({
|
||||
"click .library-header": function(event, template){
|
||||
let libs = template.librariesOpen.get();
|
||||
const libraryId = this._id;
|
||||
// Toggle whether this key is in the array or not
|
||||
if (_.contains(libs, libraryId)){
|
||||
libs = _.without(libs, libraryId);
|
||||
} else {
|
||||
libs.push(libraryId);
|
||||
}
|
||||
template.librariesOpen.set(libs);
|
||||
},
|
||||
"click .editLibrary": function(event, instance){
|
||||
event.stopPropagation();
|
||||
var libraryId = this._id;
|
||||
pushDialogStack({
|
||||
template: "libraryDialog",
|
||||
data: {libraryId},
|
||||
element: event.currentTarget.parentElement.parentElement,
|
||||
returnElement: () => instance.find(`.library-header[data-id='${libraryId}']`),
|
||||
});
|
||||
},
|
||||
"click .addItem": function(event, instance){
|
||||
event.stopPropagation();
|
||||
var libraryId = this._id;
|
||||
var itemId = LibraryItems.insert({
|
||||
name: "New Library Item",
|
||||
library: libraryId,
|
||||
});
|
||||
pushDialogStack({
|
||||
template: "libraryItemDialog",
|
||||
data: {itemId},
|
||||
element: event.currentTarget,
|
||||
returnElement: () => instance.find(`.item-name[data-id='${itemId}']`),
|
||||
});
|
||||
},
|
||||
"click .item-name": function(event, instance){
|
||||
event.stopPropagation();
|
||||
var itemId = this._id;
|
||||
pushDialogStack({
|
||||
template: "libraryItemDialog",
|
||||
data: {itemId},
|
||||
element: event.currentTarget,
|
||||
returnElement: () => instance.find(`.item-name[data-id='${itemId}']`),
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<template name="libraryItemDialog">
|
||||
<div class="fit base-dialog layout vertical">
|
||||
<app-toolbar>
|
||||
<paper-icon-button id="backButton"
|
||||
icon="arrow-back">
|
||||
</paper-icon-button>
|
||||
<div main-title>{{item.name}}</div>
|
||||
<paper-icon-button id="deleteButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
icon="delete">
|
||||
</paper-icon-button>
|
||||
</app-toolbar>
|
||||
<div class="form flex scroll-y" style="position: relative;">
|
||||
<paper-input id="libraryItemLibraryNameInput" class="fullwidth" label="Library name (optional)" value={{item.libraryName}}></paper-input>
|
||||
<paper-input id="libraryItemNameInput" class="fullwidth" label="Item name" value={{item.name}}></paper-input>
|
||||
<div class="layout horizontal center wrap">
|
||||
<paper-input id="libraryItemPluralInput" class="flex" label="Plural name" value={{item.plural}}></paper-input>
|
||||
<paper-input id="libraryItemQuantityInput" class="flex" label="Quantity" type="number" value={{item.quantity}}></paper-input>
|
||||
<paper-checkbox id="incrementCheckbox" class="flex" checked={{item.settings.showIncrement}}>
|
||||
Show Increment
|
||||
</paper-checkbox>
|
||||
</div>
|
||||
<div class="layout horizontal center wrap">
|
||||
<paper-input id="libraryItemValueInput" class="flex" label="Value" type="number" value={{item.value}}></paper-input>
|
||||
<paper-input id="libraryItemWeightInput" class="flex" label="Weight" type="number" value={{item.weight}}></paper-input>
|
||||
<paper-checkbox id="attunementCheckbox" class="flex" checked={{item.requiresAttunement}}>
|
||||
Requires Attunement
|
||||
</paper-checkbox>
|
||||
</div>
|
||||
<paper-textarea id="libraryItemDescriptionInput" label="Description" value={{item.description}}></paper-textarea>
|
||||
<div style="margin-top: 8px;">
|
||||
<div class="paper-font-subhead">Effects</div>
|
||||
{{#each indexedEffects}}
|
||||
<div class="effect layout horizontal center wrap">
|
||||
<paper-dropdown-menu label="Operation" class="operationMenu">
|
||||
<paper-listbox class="dropdown-content" selected={{operationIndex operation}}>
|
||||
<paper-item label="Base Value" name="base"> Base Value </paper-item>
|
||||
<paper-item label="Add" name="add"> Add </paper-item>
|
||||
<paper-item label="Multiply" name="mul"> Multiply </paper-item>
|
||||
<paper-item label="Min" name="min"> Min </paper-item>
|
||||
<paper-item label="Max" name="max"> Max </paper-item>
|
||||
<paper-item label="Advantage" name="advantage"> Advantage </paper-item>
|
||||
<paper-item label="Disadvantage" name="disadvantage"> Disadvantage </paper-item>
|
||||
<paper-item label="PassiveAdd" name="passiveAdd"> PassiveAdd </paper-item>
|
||||
<paper-item label="Fail" name="fail"> Fail </paper-item>
|
||||
<paper-item label="Conditional" name="conditional"> Conditional </paper-item>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-input class="LibraryItemEffectStat flex" label="Attribute" value={{stat}}></paper-input>
|
||||
<paper-input class="LibraryItemEffectValue flex" label="Value" value={{calculationOrValue}}></paper-input>
|
||||
<paper-icon-button icon="delete" class="deleteEffect"></paper-icon-button>
|
||||
</div>
|
||||
{{/each}}
|
||||
<paper-button id="addEffect" class="red-button">Add Effect</paper-button>
|
||||
</div>
|
||||
<div style="margin-top: 8px;">
|
||||
<div class="paper-font-subhead">Attacks</div>
|
||||
{{#each indexedAttacks}}
|
||||
<div class="effect layout horizontal center wrap">
|
||||
<paper-input class="LibraryItemAttackBonusInput flex" label="Attack Bonus" value={{attackBonus}}></paper-input>
|
||||
<paper-input class="LibraryItemAttackDamageInput flex" label="Damage" value={{damage}}></paper-input>
|
||||
<paper-input class="LibraryItemAttackDetailsInput flex" label="Details" value={{details}}></paper-input>
|
||||
<paper-dropdown-menu label="Damage Type" class="damageTypeMenu">
|
||||
<paper-listbox class="dropdown-content" selected={{damageTypeIndex damageType}}>
|
||||
<paper-item label="Bludgeoning" name="bludgeoning"> Bludgeoning </paper-item>
|
||||
<paper-item label="Piercing" name="piercing"> Piercing </paper-item>
|
||||
<paper-item label="Slashing" name="slashing"> Slashing </paper-item>
|
||||
<paper-item label="Acid" name="acid"> Acid </paper-item>
|
||||
<paper-item label="Cold" name="cold"> Cold </paper-item>
|
||||
<paper-item label="Fire" name="fire"> Fire </paper-item>
|
||||
<paper-item label="Force" name="force"> Force </paper-item>
|
||||
<paper-item label="Lightning" name="lightning"> Lightning </paper-item>
|
||||
<paper-item label="Necrotic" name="necrotic"> Necrotic </paper-item>
|
||||
<paper-item label="Poison" name="poison"> Poison </paper-item>
|
||||
<paper-item label="Psychic" name="psychic"> Psychic </paper-item>
|
||||
<paper-item label="Radiant" name="radiant"> Radiant </paper-item>
|
||||
<paper-item label="Thunder" name="thunder"> Thunder </paper-item>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-icon-button icon="delete" class="deleteAttack"></paper-icon-button>
|
||||
</div>
|
||||
{{/each}}
|
||||
<paper-button id="addAttack" class="red-button">Add Attack</paper-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,204 @@
|
||||
Template.libraryItemDialog.helpers({
|
||||
item(){
|
||||
return LibraryItems.findOne(this.itemId);
|
||||
},
|
||||
calculationOrValue(){
|
||||
return this.calculation || this.value;
|
||||
},
|
||||
indexedEffects(){
|
||||
let item = LibraryItems.findOne(this.itemId);
|
||||
if (!item) return;
|
||||
return _.map(item.effects, (effect, index) => {
|
||||
if (!effect) return;
|
||||
effect.index = index;
|
||||
return effect;
|
||||
});
|
||||
},
|
||||
indexedAttacks(){
|
||||
let item = LibraryItems.findOne(this.itemId);
|
||||
if (!item) return;
|
||||
return _.map(item.attacks, (attack, index) => {
|
||||
if (!attack) return;
|
||||
attack.index = index;
|
||||
return attack;
|
||||
});
|
||||
},
|
||||
operationIndex(operation){
|
||||
const ref = {
|
||||
base: 0,
|
||||
add: 1,
|
||||
mul: 2,
|
||||
min: 3,
|
||||
max: 4,
|
||||
advantage: 5,
|
||||
disadvantage: 6,
|
||||
passiveAdd: 7,
|
||||
fail: 8,
|
||||
conditional: 9,
|
||||
};
|
||||
return ref[operation];
|
||||
},
|
||||
damageTypeIndex(damageType){
|
||||
const ref = {
|
||||
bludgeoning: 0,
|
||||
piercing: 1,
|
||||
slashing: 2,
|
||||
acid: 3,
|
||||
cold: 4,
|
||||
fire: 5,
|
||||
force: 6,
|
||||
lightning: 7,
|
||||
necrotic: 8,
|
||||
poison: 9,
|
||||
psychic: 10,
|
||||
radiant: 11,
|
||||
thunder: 12,
|
||||
};
|
||||
return ref[damageType];
|
||||
}
|
||||
});
|
||||
|
||||
const bind = function(field){
|
||||
return _.debounce(function(event){
|
||||
const input = event.currentTarget;
|
||||
var value = input.value;
|
||||
LibraryItems.update(this.itemId, {
|
||||
$set: {[field]: value}
|
||||
}, {
|
||||
removeEmptyStrings: false,
|
||||
trimStrings: false,
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
|
||||
Template.libraryItemDialog.events({
|
||||
"click #backButton": function(){
|
||||
popDialogStack();
|
||||
},
|
||||
"click #deleteButton": function(){
|
||||
LibraryItems.remove(this.itemId);
|
||||
popDialogStack();
|
||||
},
|
||||
"input #libraryItemLibraryNameInput": bind("libraryName"),
|
||||
"input #libraryItemNameInput": bind("name"),
|
||||
"input #libraryItemPluralInput": bind("plural"),
|
||||
"input #libraryItemQuantityInput": bind("quantity"),
|
||||
"input #libraryItemValueInput": bind("value"),
|
||||
"input #libraryItemWeightInput": bind("weight"),
|
||||
"change #attunementCheckbox": function(event){
|
||||
LibraryItems.update(this.itemId, {
|
||||
$set: {requiresAttunement: event.currentTarget.checked}
|
||||
});
|
||||
},
|
||||
"change #incrementCheckbox": function(event){
|
||||
LibraryItems.update(this.itemId, {
|
||||
$set: {"settings.showIncrement": event.currentTarget.checked}
|
||||
});
|
||||
},
|
||||
"input #libraryItemDescriptionInput": bind("description"),
|
||||
|
||||
// Effects
|
||||
"click #addEffect": function(event, template){
|
||||
LibraryItems.update(template.data.itemId, {
|
||||
$push: {
|
||||
effects: {operation: "add"}
|
||||
}
|
||||
});
|
||||
},
|
||||
"iron-select .operationMenu": function(event, template){
|
||||
var detail = event.originalEvent.detail;
|
||||
var opName = detail.item.getAttribute("name");
|
||||
if (opName == this.operation) return;
|
||||
Meteor.call("updateLibraryItemEffect", {
|
||||
itemId: template.data.itemId,
|
||||
effectIndex: this.index,
|
||||
field: "operation",
|
||||
value: opName,
|
||||
});
|
||||
},
|
||||
"input .LibraryItemEffectStat": _.debounce(function(event, template){
|
||||
Meteor.call("updateLibraryItemEffect", {
|
||||
itemId: template.data.itemId,
|
||||
effectIndex: this.index,
|
||||
field: "stat",
|
||||
value: event.currentTarget.value,
|
||||
});
|
||||
}, 300),
|
||||
"input .LibraryItemEffectValue": _.debounce(function(event, template){
|
||||
let value = event.currentTarget.value;
|
||||
if (value && _.isFinite(+value)){
|
||||
Meteor.call("updateLibraryItemEffect", {
|
||||
itemId: template.data.itemId,
|
||||
effectIndex: this.index,
|
||||
field: "value",
|
||||
unsetField: "calculation",
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
Meteor.call("updateLibraryItemEffect", {
|
||||
itemId: template.data.itemId,
|
||||
effectIndex: this.index,
|
||||
field: "calculation",
|
||||
unsetField: "value",
|
||||
value,
|
||||
});
|
||||
}
|
||||
}, 300),
|
||||
"click .deleteEffect": function (event, template) {
|
||||
Meteor.call("removeLibraryItemEffect", {
|
||||
itemId: template.data.itemId,
|
||||
effectIndex: this.index,
|
||||
});
|
||||
},
|
||||
|
||||
// Attacks
|
||||
"click #addAttack": function(event, template){
|
||||
LibraryItems.update(template.data.itemId, {
|
||||
$push: {
|
||||
attacks: {damageType: "slashing"}
|
||||
}
|
||||
});
|
||||
},
|
||||
"iron-select .damageTypeMenu": function(event, template){
|
||||
var detail = event.originalEvent.detail;
|
||||
var damageType = detail.item.getAttribute("name");
|
||||
if (damageType == this.damageType) return;
|
||||
Meteor.call("updateLibraryItemAttack", {
|
||||
itemId: template.data.itemId,
|
||||
attackIndex: this.index,
|
||||
field: "damageType",
|
||||
value: damageType,
|
||||
});
|
||||
},
|
||||
"input .LibraryItemAttackBonusInput": _.debounce(function(event, template){
|
||||
Meteor.call("updateLibraryItemAttack", {
|
||||
itemId: template.data.itemId,
|
||||
attackIndex: this.index,
|
||||
field: "attackBonus",
|
||||
value: event.currentTarget.value,
|
||||
});
|
||||
}, 300),
|
||||
"input .LibraryItemAttackDamageInput": _.debounce(function(event, template){
|
||||
Meteor.call("updateLibraryItemAttack", {
|
||||
itemId: template.data.itemId,
|
||||
attackIndex: this.index,
|
||||
field: "damage",
|
||||
value: event.currentTarget.value,
|
||||
});
|
||||
}, 300),
|
||||
"input .LibraryItemAttackDetailsInput": _.debounce(function(event, template){
|
||||
Meteor.call("updateLibraryItemAttack", {
|
||||
itemId: template.data.itemId,
|
||||
attackIndex: this.index,
|
||||
field: "details",
|
||||
value: event.currentTarget.value,
|
||||
});
|
||||
}, 300),
|
||||
|
||||
"click .deleteAttack": function (event, template) {
|
||||
Meteor.call("removeLibraryItemAttack", {
|
||||
itemId: template.data.itemId,
|
||||
attackIndex: this.index,
|
||||
});
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user