Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43c4122fe3 | ||
|
|
3f4dcc146a | ||
|
|
e4600decd0 | ||
|
|
f6df716870 | ||
|
|
b99da301cd | ||
|
|
0a01885300 | ||
|
|
5cb1515235 | ||
|
|
7430c2c795 | ||
|
|
39f7548b8d | ||
|
|
c4a8c4b7ba |
@@ -20,3 +20,7 @@ mike:mocha
|
||||
dburles:mongo-collection-instances
|
||||
percolate:migrations
|
||||
ecwyne:mathjs
|
||||
useraccounts:polymer
|
||||
accounts-google
|
||||
splendido:accounts-meld
|
||||
email
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
accounts-base@1.2.0
|
||||
accounts-google@1.0.4
|
||||
accounts-oauth@1.1.5
|
||||
accounts-password@1.1.1
|
||||
accounts-ui@1.1.5
|
||||
accounts-ui-unstyled@1.1.7
|
||||
aldeed:collection2@2.3.3
|
||||
aldeed:simple-schema@1.3.2
|
||||
aldeed:simple-schema@1.3.3
|
||||
amplify@1.0.0
|
||||
autoupdate@1.2.1
|
||||
base64@1.0.3
|
||||
@@ -24,6 +26,7 @@ ejson@1.0.6
|
||||
email@1.0.6
|
||||
fastclick@1.0.3
|
||||
geojson-utils@1.0.3
|
||||
google@1.1.5
|
||||
html-tools@1.0.4
|
||||
htmljs@1.0.4
|
||||
http@1.1.0
|
||||
@@ -44,20 +47,22 @@ less@1.0.14
|
||||
livedata@1.0.13
|
||||
localstorage@1.0.3
|
||||
logging@1.0.7
|
||||
matb33:collection-hooks@0.7.11
|
||||
matb33:collection-hooks@0.7.13
|
||||
meteor@1.1.6
|
||||
meteor-platform@1.2.2
|
||||
mike:mocha@0.5.3
|
||||
mike:mocha@0.5.4
|
||||
minifiers@1.1.5
|
||||
minimongo@1.0.8
|
||||
mobile-status-bar@1.0.3
|
||||
momentjs:moment@2.10.3
|
||||
mongo@1.1.0
|
||||
npm-bcrypt@0.7.8_2
|
||||
oauth@1.1.4
|
||||
oauth2@1.1.3
|
||||
observe-sequence@1.0.6
|
||||
ordered-dict@1.0.3
|
||||
package-version-parser@3.0.3
|
||||
percolate:migrations@0.7.3
|
||||
percolate:migrations@0.7.5
|
||||
practicalmeteor:chai@1.9.2_3
|
||||
practicalmeteor:loglevel@1.1.0_3
|
||||
random@1.0.3
|
||||
@@ -72,16 +77,21 @@ sanjo:meteor-version@1.0.0
|
||||
service-configuration@1.0.4
|
||||
session@1.1.0
|
||||
sha@1.0.3
|
||||
softwarerero:accounts-t9n@1.0.9
|
||||
spacebars@1.0.6
|
||||
spacebars-compiler@1.0.6
|
||||
splendido:accounts-emails-field@1.2.0
|
||||
splendido:accounts-meld@1.3.0
|
||||
srp@1.0.3
|
||||
templating@1.1.1
|
||||
tracker@1.0.7
|
||||
ui@1.0.6
|
||||
underscore@1.0.3
|
||||
url@1.0.4
|
||||
useraccounts:core@1.9.1
|
||||
useraccounts:polymer@1.9.1
|
||||
velocity:chokidar@0.12.6_1
|
||||
velocity:core@0.6.0
|
||||
velocity:core@0.6.1
|
||||
velocity:html-reporter@0.5.3
|
||||
velocity:meteor-internals@1.1.0_7
|
||||
velocity:shim@0.1.0
|
||||
|
||||
@@ -19,6 +19,156 @@ Schemas.Item = new SimpleSchema({
|
||||
|
||||
Items.attachSchema(Schemas.Item);
|
||||
|
||||
var checkMovePermission = function(itemId, parent) {
|
||||
var item = Items.findOne(itemId);
|
||||
if (!item)
|
||||
throw new Meteor.Error("No such item",
|
||||
"An item could not be found to move");
|
||||
//handle permissions
|
||||
var permission = Meteor.call("canWriteCharacter", item.charId);
|
||||
if (!permission){
|
||||
throw new Meteor.Error("Access denied",
|
||||
"Not permitted to move items from this character");
|
||||
}
|
||||
if (parent.collection === "Characters"){
|
||||
permission = Meteor.call("canWriteCharacter", parent.id);
|
||||
if (!permission){
|
||||
throw new Meteor.Error("Access denied",
|
||||
"Not permitted to move items to this character");
|
||||
}
|
||||
} else {
|
||||
var parentCollectionObject = global[parent.collection];
|
||||
var parentObject = null;
|
||||
if (parentCollectionObject)
|
||||
parentObject = parentCollectionObject.findOne(
|
||||
parent.id, {fields: {_id: 1, charId: 1}}
|
||||
);
|
||||
if (!parentObject) throw new Meteor.Error(
|
||||
"Invalid parent",
|
||||
"The destination parent " + parent.id +
|
||||
" does not exist in the collection " + parent.collection
|
||||
);
|
||||
if (parentObject.charId){
|
||||
permission = Meteor.call("canWriteCharacter", parentObject.charId);
|
||||
if (!permission){
|
||||
throw new Meteor.Error("Access denied",
|
||||
"Not permitted to move items to this character");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var moveItem = function(itemId, enable, parentCollection, parentId) {
|
||||
var item = Items.findOne(itemId);
|
||||
if (!item) return;
|
||||
parentCollection = parentCollection || item.parent.collection;
|
||||
parentId = parentId || item.parent.id;
|
||||
|
||||
if (Meteor.isServer) {
|
||||
checkMovePermission(itemId, {collection: parentCollection, id: parentId});
|
||||
}
|
||||
|
||||
//update the item provided the update will actually change something
|
||||
if (
|
||||
item.parent.collection !== parentCollection ||
|
||||
item.parent.id !== parentId ||
|
||||
item.enabled !== enable
|
||||
){
|
||||
Items.update(
|
||||
itemId,
|
||||
{$set: {
|
||||
"parent.collection": parentCollection,
|
||||
"parent.id": parentId,
|
||||
enabled: enable,
|
||||
}}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
moveItemToParent: function(itemId, parent) {
|
||||
check(itemId, String);
|
||||
check(parent, {collection: String, id: String});
|
||||
moveItem(itemId, false, parent.collection, parent.id);
|
||||
},
|
||||
moveItemToCharacter: function(itemId, charId) {
|
||||
check(itemId, String);
|
||||
check(charId, String);
|
||||
moveItem(itemId, false, "Characters", charId);
|
||||
},
|
||||
moveItemToContainer: function(itemId, containerId) {
|
||||
check(itemId, String);
|
||||
check(containerId, String);
|
||||
moveItem(itemId, false, "Containers", containerId);
|
||||
},
|
||||
equipItem: function(itemId, charId){
|
||||
check(itemId, String);
|
||||
check(charId, String);
|
||||
moveItem(itemId, true, "Characters", charId);
|
||||
},
|
||||
unequipItem: function(itemId, charId){
|
||||
check(itemId, String);
|
||||
check(charId, String);
|
||||
moveItem(itemId, false, "Characters", charId);
|
||||
},
|
||||
splitItemToParent: function(itemId, moveQuantity, parent){
|
||||
check(itemId, String);
|
||||
check(moveQuantity, Number);
|
||||
check(parent, {id: String, collection: String});
|
||||
|
||||
//get the item
|
||||
var item = Items.findOne(itemId);
|
||||
if (!item) return;
|
||||
|
||||
//don't bother moving nothing
|
||||
if (moveQuantity <= 0 || item.quantity <= 0){
|
||||
return;
|
||||
}
|
||||
//ensure we are only moving up to the current stack size
|
||||
if (item.quantity < moveQuantity){
|
||||
moveQuantity = this.quantity;
|
||||
}
|
||||
|
||||
if (Meteor.isServer) {
|
||||
checkMovePermission(itemId, parent);
|
||||
}
|
||||
|
||||
//create a new item stack
|
||||
var newStack = _.omit(EJSON.clone(item), "_id");
|
||||
newStack.parent = parent;
|
||||
newStack.quantity = moveQuantity;
|
||||
|
||||
//find out if we have an exact replica in the destination
|
||||
var query = _.omit(newStack, ["parent", "quantity"]);
|
||||
query["parent.collection"] = newStack.parent.collection;
|
||||
query["parent.id"] = newStack.parent.id;
|
||||
query._id = {$ne: itemId}; //make sure we don't join it to itself
|
||||
var existingStack = Items.findOne(query);
|
||||
if (existingStack){
|
||||
//increase the existing stack's size
|
||||
Items.update(
|
||||
existingStack._id,
|
||||
{$inc: {quantity: moveQuantity}}
|
||||
);
|
||||
} else {
|
||||
//insert the new stack
|
||||
Items.insert(newStack, function(err, id){
|
||||
if (err) throw err;
|
||||
//copy the children also
|
||||
Meteor.call("cloneChildren", item._id, {collection: "Items", id: id});
|
||||
});
|
||||
}
|
||||
|
||||
//reduce the old stack's size
|
||||
var oldQuantity = item.quantity - moveQuantity;
|
||||
if (oldQuantity === 0){
|
||||
Items.remove(itemId);
|
||||
} else {
|
||||
Items.update(itemId, {$set: {quantity: oldQuantity}});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Items.helpers({
|
||||
totalValue: function(){
|
||||
return this.value * this.quantity;
|
||||
@@ -33,103 +183,6 @@ Items.helpers({
|
||||
return this.name;
|
||||
}
|
||||
},
|
||||
equip: function(characterId){
|
||||
var charId = characterId || this.charId;
|
||||
if (!charId || !Characters.findOne(charId)) throw "Invalid character";
|
||||
if (this.parent.collection === "Characters" &&
|
||||
this.parent.id === charId &&
|
||||
this.enabled) {
|
||||
return;
|
||||
}
|
||||
Items.update(
|
||||
this._id,
|
||||
{$set: {
|
||||
"parent.collection": "Characters",
|
||||
"parent.id": charId,
|
||||
enabled: true,
|
||||
}}
|
||||
);
|
||||
},
|
||||
unequip: function(){
|
||||
if (!this.enabled) return;
|
||||
Items.update(this._id, {$set: {enabled: false}});
|
||||
},
|
||||
moveToContainer: function(containerId){
|
||||
if (!containerId || !Containers.findOne(containerId)){
|
||||
throw "Invalid container";
|
||||
}
|
||||
if (this.parent.collection === "Containers" &&
|
||||
this.parent.id === containerId &&
|
||||
!this.enabled) return;
|
||||
Items.update(
|
||||
this._id,
|
||||
{$set: {
|
||||
"parent.collection": "Containers",
|
||||
"parent.id": containerId,
|
||||
enabled: false,
|
||||
}}
|
||||
);
|
||||
},
|
||||
moveToCharacter: function(characterId){
|
||||
if (!characterId || !Characters.findOne(characterId)) {
|
||||
throw "Invalid character";
|
||||
}
|
||||
if (this.parent.collection === "Characters" &&
|
||||
this.parent.id === characterId &&
|
||||
!this.enabled) return;
|
||||
Items.update(
|
||||
this._id,
|
||||
{$set: {
|
||||
"parent.collection": "Characters",
|
||||
"parent.id": characterId,
|
||||
charId: characterId,
|
||||
enabled: false,
|
||||
}}
|
||||
);
|
||||
},
|
||||
splitToParent: function(parent, moveQuantity){
|
||||
check(parent, {id: String, collection: String});
|
||||
check(moveQuantity, Number);
|
||||
var parentCollection = Meteor.isClient ?
|
||||
window[parent.collection] : global[parent.collection];
|
||||
if (!parent.id || !parentCollection.findOne(parent.id)){
|
||||
throw "Invalid parent";
|
||||
}
|
||||
var oldStack = this;
|
||||
//we can only move as much as we have, leaving 0 behind at worst
|
||||
if (oldStack.quantity < moveQuantity) moveQuantity = oldStack.quantity;
|
||||
var oldQuantity = oldStack.quantity - moveQuantity;
|
||||
|
||||
var newStack = _.pick(oldStack, Schemas.Item.objectKeys());
|
||||
newStack.parent = parent;
|
||||
newStack.quantity = moveQuantity;
|
||||
|
||||
var existingStack = Items.findOne(_.omit(newStack, "quantity"));
|
||||
var updateStackSize = function(){
|
||||
if (oldQuantity > 0){
|
||||
Items.update(oldStack._id, {$set: {quantity: oldQuantity}});
|
||||
} else {
|
||||
Items.remove(oldStack._id);
|
||||
}
|
||||
};
|
||||
if (existingStack){
|
||||
Items.update(
|
||||
existingStack._id,
|
||||
{$inc: {quantity: moveQuantity}},
|
||||
{},
|
||||
function(){
|
||||
updateStackSize();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Items.insert(newStack, function(err, id){
|
||||
if (err) throw err;
|
||||
updateStackSize();
|
||||
//copy the children also
|
||||
Meteor.call("cloneChildren", oldStack._id, {collection: "Items", id: id});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Items.before.update(function(userId, doc, fieldNames, modifier, options){
|
||||
|
||||
@@ -1,44 +1,23 @@
|
||||
Schema = {};
|
||||
|
||||
Schema.User = new SimpleSchema({
|
||||
username: {
|
||||
type: String,
|
||||
regEx: /^[a-z0-9A-Z_]{3,15}$/,
|
||||
optional: true,
|
||||
},
|
||||
emails: {
|
||||
type: [Object],
|
||||
// this must be optional if you also use other login services like facebook,
|
||||
// but if you use only accounts-password, then it can be required
|
||||
optional: true,
|
||||
},
|
||||
"emails.$.address": {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Email,
|
||||
},
|
||||
"emails.$.verified": {
|
||||
type: Boolean
|
||||
},
|
||||
createdAt: {
|
||||
type: Date
|
||||
},
|
||||
services: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
blackbox: true,
|
||||
},
|
||||
roles: {
|
||||
type: [String],
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.attachSchema(Schema.User);
|
||||
|
||||
Meteor.users.allow({
|
||||
update: function(userId, doc, fields, modifier) {
|
||||
return userId === doc._id &&
|
||||
fields.length === 1 &&
|
||||
fields[0] === "username";
|
||||
if (
|
||||
doc._id === userId &&
|
||||
_.contains(fields, "username") &&
|
||||
_.contains(fields, "profile") &&
|
||||
fields.length === 2 &&
|
||||
_.keys(modifier).length === 1 &&
|
||||
modifier.$set &&
|
||||
modifier.$set["profile.username"] &&
|
||||
modifier.$set.username &&
|
||||
_.keys(modifier.$set).length === 2
|
||||
){
|
||||
var expectedUsername = modifier.$set["profile.username"];
|
||||
expectedUsername = expectedUsername.toLowerCase().replace(/\s+/gm, "");
|
||||
if (modifier.$set.username !== expectedUsername){
|
||||
return false;
|
||||
}
|
||||
var foundUser = Meteor.call("getUserId", expectedUsername);
|
||||
return !foundUser || foundUser === userId;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,6 +3,23 @@ Router.configure({
|
||||
layoutTemplate: "layout",
|
||||
});
|
||||
|
||||
Router.plugin("ensureSignedIn", {
|
||||
except: [
|
||||
"home",
|
||||
"atSignIn",
|
||||
"atSignUp",
|
||||
"atForgotPassword",
|
||||
"atResetPwd",
|
||||
"atEnrollAccount",
|
||||
"atVerifyEmail",
|
||||
"atresendVerificationEmail",
|
||||
"loginButtons",
|
||||
"notFound",
|
||||
]
|
||||
});
|
||||
|
||||
Router.plugin("dataNotFound", {notFoundTemplate: "notFound"});
|
||||
|
||||
Router.map(function() {
|
||||
this.route("/", {
|
||||
name: "home",
|
||||
@@ -56,4 +73,8 @@ Router.map(function() {
|
||||
document.title = appName + " Account";
|
||||
},
|
||||
});
|
||||
|
||||
this.route("/loginButtons", {
|
||||
name: "loginButtons",
|
||||
})
|
||||
});
|
||||
|
||||
@@ -174,71 +174,93 @@ Template.inventoryItem.helpers({
|
||||
|
||||
Template.layout.events({
|
||||
"dragstart .inventoryItem": function(event, instance){
|
||||
event.originalEvent.dataTransfer.setData("dicecloud-id/items", this._id);
|
||||
Session.set("inventory.dragItemId", this._id);
|
||||
Session.set("inventory.dragItemOriginalContainer", this.container);
|
||||
Session.set("inventory.dragItemOriginalCharacter", this.charId);
|
||||
},
|
||||
"dragover .itemContainer, dragenter .itemContainer":
|
||||
function(event, instance){
|
||||
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
"dragover .equipmentContainer, dragenter .equipmentContainer":
|
||||
function(event, instance){
|
||||
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
"dragover .carriedContainer, dragenter .carriedContainer":
|
||||
function(event, instance){
|
||||
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
"dragover .characterRepresentative, dragenter .characterRepresentative":
|
||||
function(event, instance){
|
||||
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
"dragend .inventoryItem": function(event, instance){
|
||||
resetInvetorySession(); //this is a valid drop zone
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
"dragover .itemContainer": function(event, instance){
|
||||
event.preventDefault();
|
||||
},
|
||||
"dragover .equipmentContainer": function(event, instance){
|
||||
event.preventDefault();
|
||||
},
|
||||
"dragover .carriedContainer": function(event, instance){
|
||||
event.preventDefault();
|
||||
},
|
||||
"drop .itemContainer": function(event, instacne){
|
||||
var item = Items.findOne(Session.get("inventory.dragItemId"));
|
||||
"drop .itemContainer": function(event, instance){
|
||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||
if (event.ctrlKey){
|
||||
//split the stack to the container
|
||||
GlobalUI.showDialog({
|
||||
template: "splitStackDialog",
|
||||
data: {
|
||||
id: item._id,
|
||||
id: itemId,
|
||||
parentCollection: "Containers",
|
||||
parentId: this._id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
//move item to the container
|
||||
item.moveToContainer(this._id);
|
||||
Meteor.call("moveItemToContainer", itemId, this._id);
|
||||
}
|
||||
resetInvetorySession();
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
"drop .equipmentContainer": function(event, instance){
|
||||
var charId = Session.get("inventory.dragItemOriginalCharacter");
|
||||
var item = Items.findOne(Session.get("inventory.dragItemId"));
|
||||
item.equip(charId);
|
||||
resetInvetorySession();
|
||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||
Meteor.call("equipItem", itemId, this._id);
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
"drop .carriedContainer": function(event, instance){
|
||||
var charId = Session.get("inventory.dragItemOriginalCharacter");
|
||||
var item = Items.findOne(Session.get("inventory.dragItemId"));
|
||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||
if (event.ctrlKey){
|
||||
//split the stack to the container
|
||||
GlobalUI.showDialog({
|
||||
template: "splitStackDialog",
|
||||
data: {
|
||||
id: item._id,
|
||||
id: itemId,
|
||||
parentCollection: "Characters",
|
||||
parentId: this._id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
//move item to the character
|
||||
item.moveToCharacter(this._id);
|
||||
Meteor.call("moveItemToCharacter", itemId, this._id);
|
||||
}
|
||||
resetInvetorySession();
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
"drop .characterRepresentative": function(event, instance) {
|
||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||
if (event.ctrlKey){
|
||||
//split the stack to the container
|
||||
GlobalUI.showDialog({
|
||||
template: "splitStackDialog",
|
||||
data: {
|
||||
id: itemId,
|
||||
parentCollection: "Characters",
|
||||
parentId: this._id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
//move item to the character
|
||||
Meteor.call("moveItemToCharacter", itemId, this._id);
|
||||
}
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
});
|
||||
|
||||
var resetInvetorySession = function(){
|
||||
_.defer(function(){
|
||||
Session.set("inventory.dragItemId", null);
|
||||
Session.set("inventory.dragItemOriginalContainer", null);
|
||||
Session.set("inventory.dragItemOriginalCharacter", null);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -81,13 +81,10 @@ Template.itemEdit.events({
|
||||
},
|
||||
"change #equippedInput": function(event){
|
||||
var equipped = Template.instance().find("#equippedInput").checked;
|
||||
var item = Items.findOne(this._id);
|
||||
if (item){
|
||||
if (equipped){
|
||||
item.equip();
|
||||
} else {
|
||||
item.unequip();
|
||||
}
|
||||
if (equipped){
|
||||
Meteor.call("equipItem", this._id, this.charId);
|
||||
} else {
|
||||
Meteor.call("unequipItem", this._id, this.charId);
|
||||
}
|
||||
},
|
||||
"change #attunementCheckbox": function(event){
|
||||
@@ -107,7 +104,6 @@ Template.containerDropdown.events({
|
||||
var detail = event.originalEvent.detail;
|
||||
if (!detail.isSelected) return;
|
||||
var containerId = detail.item.getAttribute("name");
|
||||
var item = Items.findOne(Template.currentData()._id);
|
||||
item.moveToContainer(containerId);
|
||||
Meteor.call("moveItemToContainer", Template.currentData()._id, containerId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,13 +7,12 @@ Template.splitStackDialog.helpers({
|
||||
|
||||
Template.splitStackDialog.events({
|
||||
"tap #moveButton": function(event, instance){
|
||||
var item = Items.findOne(this.id);
|
||||
if (item){
|
||||
item.splitToParent(
|
||||
{collection: this.parentCollection , id: this.parentId},
|
||||
+instance.find("#quantityInput").value
|
||||
);
|
||||
}
|
||||
Meteor.call(
|
||||
"splitItemToParent",
|
||||
this.id,
|
||||
+instance.find("#quantityInput").value,
|
||||
{collection: this.parentCollection , id: this.parentId}
|
||||
);
|
||||
},
|
||||
"tap #oneButton":function(event, instance){
|
||||
instance.find("#quantityInput").value = 1;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{{#if characters.count}}
|
||||
<div>
|
||||
{{#each characters}}
|
||||
<div class="singleLineItem">{{name}}</div>
|
||||
<div class="singleLineItem characterRepresentative">{{name}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<p>You need to swim through a sunken section of dungeon to fetch the quest's Thing.<br>You'll need to take off your magical Plate Armor of +1 Constitution to swim without sinking, of course. Taking it off will change your armor class, your speed and your constitution, which in turn changes your hitpoints and your constitution saving throw. Working out all those changes in the middle of a game will drag the game to a hault. <br> Fortunately you have a digital character sheet, so it's a matter of dragging your Plate Armor +1 Con from your "equipment" box to your "backpack" box and you're done. Your hitpoints change correctly, your saving throws are up to date, your armor class goes back to reflecting the fact that you have natural armor from being a dragonborn. Your character sheet keeps up and you ultimately get more time to play the game. Huzzah!</p>
|
||||
<h2>Creating a Character</h2>
|
||||
<ul>
|
||||
<li>In the <a href={{pathFor route="characterList"}}>character list</a>, click the plus button, floating in the bottom left corner.</li>
|
||||
<li>In the <a href={{pathFor route="characterList"}}>character list</a>, click the plus button, floating in the bottom right corner.</li>
|
||||
<li>Give your character a name, gender and race, these can be changed later if you change your mind. Then click the Add button</li>
|
||||
<li>Your new character should open, with most of its attributes and abilities at zero.</li>
|
||||
</ul>
|
||||
@@ -41,7 +41,7 @@
|
||||
<p>Your character currently doesn't have any ability scores, so lets fix that. Whether you roll your abilities or point-buy them, lets add a feature to represent where they came from</p>
|
||||
<ul>
|
||||
<li>Navigate to the <emd>Features</emd> tab</li>
|
||||
<li>Click the plus button in the bottom left to add a new feature</li>
|
||||
<li>Click the plus button in the bottom right to add a new feature</li>
|
||||
<li>Give the Feature a name, like <em>Point Buy</em></li>
|
||||
<li>Leave the feature as always enabled, don't limit its uses, and leave the description blank</li>
|
||||
<li>Click the <em>Add Effect</em> button</li>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template name="layout">
|
||||
|
||||
<core-drawer-panel>
|
||||
<core-header-panel drawer navigation flex mode="seamed" class="white">
|
||||
<div id="accountSummary">
|
||||
{{> loginButtons}}
|
||||
{{#if currentUser}}
|
||||
<div id="profileLink" style="text-decoration: underline; cursor: pointer;">
|
||||
My account
|
||||
{{profileLink}}
|
||||
</div>
|
||||
{{else}}
|
||||
<a href="/sign-in" style="color: white;">Sign in</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div id="navPanel">
|
||||
|
||||
@@ -9,7 +9,11 @@ Template.layout.destroyed = function() {
|
||||
Template.layout.helpers({
|
||||
notSelected: function(){
|
||||
return Session.get("global.ui.detailShow") ? "not-selected" : null;
|
||||
}
|
||||
},
|
||||
profileLink: function() {
|
||||
var user = Meteor.user();
|
||||
return user.profile && user.profile.username || user.username || "My Account";
|
||||
},
|
||||
});
|
||||
|
||||
Template.layout.events({
|
||||
|
||||
11
rpg-docs/client/views/notFound/notFound.html
Normal file
11
rpg-docs/client/views/notFound/notFound.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<template name="notFound">
|
||||
<div layout vertical center center-justified fit>
|
||||
<h2>The data for the page you requested could not be found.</h2>
|
||||
{{#if currentUser}}
|
||||
<h2>It might not exist, or you might not have permission to view it.</h2>
|
||||
{{else}}
|
||||
<h2>Perhaps you need to sign in first:</h2>
|
||||
{{atForm}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -3,11 +3,7 @@
|
||||
<core-toolbar class="blue-grey white-text">
|
||||
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
|
||||
<div id="username" class="clickable" flex>
|
||||
{{#if username}}
|
||||
{{username}}
|
||||
{{else}}
|
||||
Tap to set username
|
||||
{{/if}}
|
||||
{{profileName}}
|
||||
</div>
|
||||
</core-toolbar>
|
||||
<div id="userProfile" class="padded">
|
||||
@@ -20,5 +16,7 @@
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{> atForm}}
|
||||
{{> atNavButton }}
|
||||
{{/with}}
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
Template.profile.helpers({
|
||||
profileName: function() {
|
||||
var user = Meteor.user();
|
||||
return user.profile && user.profile.username ||
|
||||
user.username ||
|
||||
"Tap to set username";
|
||||
}
|
||||
});
|
||||
|
||||
Template.profile.events({
|
||||
"tap #username": function(){
|
||||
if (this._id === Meteor.userId()){
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<template name="usernameDialog">
|
||||
{{#with currentUser}}
|
||||
<div>
|
||||
<paper-input id="usernameInput" label="Username" value={{username}}></paper-input>
|
||||
</div>
|
||||
{{/with}}
|
||||
<div>
|
||||
<paper-input id="usernameInput" label="Username" value={{profileName}}></paper-input>
|
||||
</div>
|
||||
<div style="color: red;" class="vertMargin">{{errorMessage}}</div>
|
||||
<paper-button id="cancelButton" affirmative> Cancel </paper-button>
|
||||
<paper-button id="changeButton" affirmative> Change Username </paper-button>
|
||||
<paper-button id="changeButton" disabled={{invalid}} affirmative> Change Username </paper-button>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,49 @@
|
||||
var getUsername = function() {
|
||||
var user = Meteor.user();
|
||||
return user.profile && user.profile.username || user.username;
|
||||
};
|
||||
|
||||
Template.usernameDialog.onCreated(function() {
|
||||
this.errorMessage = new ReactiveVar("");
|
||||
this.username = new ReactiveVar(getUsername());
|
||||
});
|
||||
|
||||
Template.usernameDialog.helpers({
|
||||
profileName: function() {
|
||||
return getUsername();
|
||||
},
|
||||
invalid: function() {
|
||||
return !!Template.instance().errorMessage.get();
|
||||
},
|
||||
errorMessage: function() {
|
||||
return Template.instance().errorMessage.get();
|
||||
},
|
||||
});
|
||||
|
||||
Template.usernameDialog.events({
|
||||
"change #usernameInput, input #usernameInput": function(event, instance) {
|
||||
var username = instance.find("#usernameInput").value;
|
||||
username = username.trim().toLowerCase().replace(/\s+/gm, "");
|
||||
if (username.length < 3){
|
||||
instance.errorMessage.set("Username too short");
|
||||
} else {
|
||||
instance.errorMessage.set("Validating...");
|
||||
Meteor.call("getUserId", username, function(err, userId){
|
||||
if (userId && userId !== Meteor.userId())
|
||||
instance.errorMessage.set("This username is taken");
|
||||
else
|
||||
instance.errorMessage.set("");
|
||||
});
|
||||
}
|
||||
},
|
||||
"tap #changeButton": function(event, instance){
|
||||
var username = instance.find("#usernameInput").value;
|
||||
username = username.trim().replace(/\s+/gm, " ");
|
||||
var profileName = username;
|
||||
username = username.toLowerCase().replace(/\s+/gm, "");
|
||||
Meteor.users.update(
|
||||
Meteor.userId(),
|
||||
{$set: {username: instance.find("#usernameInput").value}}
|
||||
{$set: {username: username, "profile.username": profileName}}
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
13
rpg-docs/client/views/user/titledAtForm/titledAtForm.html
Normal file
13
rpg-docs/client/views/user/titledAtForm/titledAtForm.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<template name="titledAtForm">
|
||||
<core-toolbar class="blue-grey white-text">
|
||||
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
|
||||
<div flex>
|
||||
|
||||
</div>
|
||||
</core-toolbar>
|
||||
<div class="scroll-y padded" fit layout vertical center center-justified>
|
||||
<paper-shadow class="white" style="max-width: 400px;">
|
||||
{{> atForm}}
|
||||
</paper-shadow>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,3 +1,14 @@
|
||||
Meteor.methods({
|
||||
canWriteCharacter: function(charId) {
|
||||
var userId = this.userId;
|
||||
var char = Characters.findOne(
|
||||
charId,
|
||||
{fields: {owner: 1, writers: 1}}
|
||||
);
|
||||
return (userId && char.owner === userId || _.contains(char.writers, userId));
|
||||
},
|
||||
});
|
||||
|
||||
CHARACTER_SUBSCHEMA_ALLOW = {
|
||||
// the user must be logged in, and the user must be a writer of the character
|
||||
insert: function(userId, doc) {
|
||||
|
||||
71
rpg-docs/lib/constants/useraccountsConfig.js
Normal file
71
rpg-docs/lib/constants/useraccountsConfig.js
Normal file
@@ -0,0 +1,71 @@
|
||||
AccountsTemplates.configure({
|
||||
//behaviour
|
||||
confirmPassword: true,
|
||||
enablePasswordChange: true,
|
||||
enforceEmailVerification: true,
|
||||
overrideLoginErrors: false,
|
||||
sendVerificationEmail: true,
|
||||
lowercaseUsername: true,
|
||||
//appearance
|
||||
continuousValidation: true,
|
||||
negativeValidation: true,
|
||||
negativeFeedback: true,
|
||||
showValidating: true,
|
||||
showAddRemoveServices: true,
|
||||
showForgotPasswordLink: true,
|
||||
showResendVerificationEmailLink: true,
|
||||
});
|
||||
|
||||
AccountsTemplates.configureRoute("changePwd", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("enrollAccount", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("forgotPwd", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("resetPwd", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("signIn", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("signUp", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("verifyEmail", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("resendVerificationEmail", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
|
||||
if (Meteor.isServer){
|
||||
Meteor.methods({
|
||||
"userExists": function(username){
|
||||
return !!Meteor.users.findOne({username: username});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
AccountsTemplates.addField({
|
||||
_id: "username",
|
||||
type: "text",
|
||||
required: true,
|
||||
func: function(value){
|
||||
if (Meteor.isClient) {
|
||||
var self = this;
|
||||
Meteor.call("userExists", value, function(err, userExists){
|
||||
if (!userExists)
|
||||
self.setSuccess();
|
||||
else
|
||||
self.setError("This username is taken");
|
||||
self.setValidating(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Server
|
||||
return Meteor.call("userExists", value);
|
||||
},
|
||||
});
|
||||
@@ -9,23 +9,27 @@ evaluate = function(charId, string){
|
||||
}
|
||||
//ability modifiers
|
||||
var abilityMods = [
|
||||
"STRENGTHMOD",
|
||||
"DEXTERITYMOD",
|
||||
"CONSTITUTIONMOD",
|
||||
"INTELLIGENCEMOD",
|
||||
"WISDOMMOD",
|
||||
"CHARISMAMOD",
|
||||
"strengthMod",
|
||||
"dexterityMod",
|
||||
"constitutionMod",
|
||||
"intelligenceMod",
|
||||
"wisdomMod",
|
||||
"charismaMod",
|
||||
];
|
||||
if (_.contains(abilityMods, sub.toUpperCase())){
|
||||
if (_.contains(abilityMods, sub)){
|
||||
var slice = sub.slice(0, -3);
|
||||
return char.abilityMod(slice);
|
||||
try {
|
||||
return char.abilityMod(slice);
|
||||
} catch (e){
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
//class levels
|
||||
if (/\w+levels?\b/gi.test(sub)){
|
||||
//strip out "level"
|
||||
var className = sub.replace(/levels?\b/gi, "");
|
||||
var cls = Classes.findOne({charId: charId, name: className});
|
||||
return cls && cls.level;
|
||||
return cls && cls.level || sub;
|
||||
}
|
||||
//character level
|
||||
if (sub.toUpperCase() === "LEVEL"){
|
||||
|
||||
@@ -42,6 +42,12 @@ var inheritParentProperties = function(doc, collection){
|
||||
"Document's parent does not exist"
|
||||
);
|
||||
var handMeDowns = _.pick(parent, collection.inheritedKeys);
|
||||
if (
|
||||
_.contains(collection.inheritedKeys, "charId") &&
|
||||
doc.parent.collection === "Characters"
|
||||
){
|
||||
handMeDowns.charId = doc.parent.id;
|
||||
}
|
||||
if (_.isEmpty(handMeDowns)) return;
|
||||
collection.update(doc._id, {$set: handMeDowns});
|
||||
};
|
||||
@@ -76,14 +82,13 @@ makeChild = function(collection, inheritedKeys){
|
||||
}
|
||||
});
|
||||
|
||||
if (Meteor.isClient) {
|
||||
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||
if (modifier && modifier.$set && modifier.$set.parent){
|
||||
//when we change parents, inherit its properties
|
||||
inheritParentProperties(doc, collection);
|
||||
}
|
||||
});
|
||||
}
|
||||
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||
if (modifier && modifier.$set && modifier.$set["parent.id"]){
|
||||
//when we change parents, inherit its properties
|
||||
console.log("re-inheriting")
|
||||
inheritParentProperties(doc, collection);
|
||||
}
|
||||
});
|
||||
|
||||
collection.softRemoveNode = collection.softRemoveNode || function(id){
|
||||
collection.softRemove(id);
|
||||
@@ -102,14 +107,12 @@ makeParent = function(collection, donatedKeys){
|
||||
donatedKeys = joinWithDefaultKeys(donatedKeys);
|
||||
var collectionName = collection._collection.name;
|
||||
//after changing, push the changes to all children
|
||||
if (Meteor.isClient) {
|
||||
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||
modifier = limitModifierToKeys(modifier, donatedKeys);
|
||||
doc = _.pick(doc, ["_id", "charId"]);
|
||||
if (!modifier) return;
|
||||
Meteor.call("updateChildren", doc, modifier, true);
|
||||
});
|
||||
}
|
||||
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||
modifier = limitModifierToKeys(modifier, donatedKeys);
|
||||
doc = _.pick(doc, ["_id", "charId"]);
|
||||
if (!modifier) return;
|
||||
Meteor.call("updateChildren", doc, modifier, true);
|
||||
});
|
||||
collection.softRemoveNode = function(id){
|
||||
Meteor.call("softRemoveNode", collectionName, id);
|
||||
};
|
||||
|
||||
27
rpg-docs/server/lib/configuration/accountsMeldConfig.js
Normal file
27
rpg-docs/server/lib/configuration/accountsMeldConfig.js
Normal file
@@ -0,0 +1,27 @@
|
||||
AccountsMeld.configure({
|
||||
meldDBCallback: function(sourceUserId, destinationUserId){
|
||||
// Here you can modify every collection you need for the document referencing
|
||||
// to sourceUserId to be modified in order to point to destinationUserId
|
||||
Characters.update(
|
||||
{owner: sourceUserId},
|
||||
{$set: {owner: destinationUserId}},
|
||||
{multi: true}
|
||||
);
|
||||
Characters.update(
|
||||
{writers: sourceUserId},
|
||||
{
|
||||
$pull: {writers: sourceUserId},
|
||||
$addToSet: {writers: destinationUserId},
|
||||
},
|
||||
{multi: true}
|
||||
);
|
||||
Characters.update(
|
||||
{readers: sourceUserId},
|
||||
{
|
||||
$pull: {readers: sourceUserId},
|
||||
$addToSet: {readers: destinationUserId},
|
||||
},
|
||||
{multi: true}
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
Meteor.publish("singleCharacter", function(characterId){
|
||||
userId = this.userId;
|
||||
if (!userId) return [];
|
||||
var char = Characters.findOne({
|
||||
_id: characterId,
|
||||
$or: [
|
||||
@@ -30,5 +31,7 @@ Meteor.publish("singleCharacter", function(characterId){
|
||||
{fields: {username: 1}}
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user