diff --git a/rpg-docs/Model/Campaign/Party.js b/rpg-docs/Model/Campaign/Party.js
index 990e8e11..d48dbb97 100644
--- a/rpg-docs/Model/Campaign/Party.js
+++ b/rpg-docs/Model/Campaign/Party.js
@@ -1,8 +1,42 @@
Parties = new Mongo.Collection("parties");
Schemas.Party = new SimpleSchema({
- //each character/monster can only be in one party at a time
- //each party can only be in a single instance at a time
+ name: {
+ type: String,
+ defaultValue: "New Party",
+ trim: false,
+ optional: true,
+ },
+ characters: {
+ type: [String],
+ regEx: SimpleSchema.RegEx.Id,
+ index: 1,
+ defaultValue: [],
+ },
+ owner: {
+ type: String,
+ regEx: SimpleSchema.RegEx.Id,
+ },
});
Parties.attachSchema(Schemas.Party);
+
+Parties.allow({
+ insert: function(userId, doc) {
+ return userId && doc.owner === userId;
+ },
+ update: function(userId, doc, fields, modifier) {
+ return userId && doc.owner === userId;
+ },
+ remove: function(userId, doc) {
+ return userId && doc.owner === userId;
+ },
+ fetch: ["owner"],
+});
+
+Parties.deny({
+ update: function(userId, docs, fields, modifier) {
+ // can't change owners
+ return _.contains(fields, "owner");
+ }
+});
diff --git a/rpg-docs/client/views/characterList/characterList.css b/rpg-docs/client/views/characterList/characterList.css
index f25313b8..f33acebb 100644
--- a/rpg-docs/client/views/characterList/characterList.css
+++ b/rpg-docs/client/views/characterList/characterList.css
@@ -8,8 +8,16 @@
position: relative;
}
-.character-card .image {
+.partyHeader {
+ display: inline-block;
+}
+.partyHeader iron-icon {
+ visibility: hidden;
+}
+
+.partyHeader:hover iron-icon{
+ visibility: initial;
}
.character-card .initials {
diff --git a/rpg-docs/client/views/characterList/characterList.html b/rpg-docs/client/views/characterList/characterList.html
index acde48cb..a5b00dd2 100644
--- a/rpg-docs/client/views/characterList/characterList.html
+++ b/rpg-docs/client/views/characterList/characterList.html
@@ -10,31 +10,27 @@
{{#if currentUser}}
{{#if characters.count}}
+ {{# each party in parties}}
+
+ {{#with party}}
+
+ {{/with}}
+
+ {{# each charactersInParty party._id}}
+ {{> characterCard}}
+ {{/each}}
+ {{> gridPadding class="character-card flex layout vertical" num=12}}
+
+
+ {{/each}}
{{else}}
You don't seem to have any characters yet
@@ -47,9 +43,46 @@
{{/if}}
-
+ {{#fabMenu}}
+
+
+ {{/fabMenu}}
+
+
+
+
+
+ {{#unless picture}}
+
+ {{initials name}}
+
+ {{/unless}}
+
+
+
+ {{name}}
+
+
+ {{alignment}} {{gender}} {{race}}
+
+
+
+
+
+
diff --git a/rpg-docs/client/views/characterList/characterList.js b/rpg-docs/client/views/characterList/characterList.js
index 254b2854..e35a5571 100644
--- a/rpg-docs/client/views/characterList/characterList.js
+++ b/rpg-docs/client/views/characterList/characterList.js
@@ -1,35 +1,57 @@
Template.characterList.helpers({
- characters(){
+ characters() {
var userId = Meteor.userId();
return Characters.find(
- {
- $or: [
- {readers: userId},
- {writers: userId},
- {owner: userId},
- ]
- },
- {
- fields: {
- name: 1,
- urlName: 1,
- picture: 1,
- color: 1,
- race: 1,
- alignment: 1,
- gender: 1,
- },
- sort: {name: 1},
- }
+ {$or: [{readers: userId}, {writers: userId}, {owner: userId}]},
+ {sort: {name: 1}}
);
},
+ parties() {
+ return Parties.find({owner: Meteor.userId()});
+ },
+ charactersInParty(partyId) {
+ var userId = Meteor.userId();
+ var party = Parties.findOne(partyId);
+ return Characters.find(
+ {
+ _id: {$in: party.characters},
+ $or: [{readers: userId}, {writers: userId}, {owner: userId}],
+ },
+ {sort: {name: 1}}
+ );
+ },
+ charactersWithNoParty() {
+ var userId = Meteor.userId();
+ var charArrays = Parties.find({owner: userId}).map(p => p.characters);
+ var partyChars = _.uniq(_.flatten(charArrays));
+ return Characters.find(
+ {
+ _id: {$nin: partyChars},
+ $or: [{readers: userId}, {writers: userId}, {owner: userId}],
+ },
+ {sort: {name: 1}}
+ );
+ },
+});
+
+Template.characterCard.helpers({
initials(name){
return name.replace(/[^A-Z]/g, "");
},
-})
+});
Template.characterList.events({
- "tap .addCharacter": function(event, template) {
+ "click .partyHeader": function(event, instance){
+ pushDialogStack({
+ template: "partyDialog",
+ data: {
+ _id: this._id,
+ startEditing: true,
+ },
+ element: event.currentTarget.parentElement,
+ });
+ },
+ "click .addCharacter": function(event, instance) {
pushDialogStack({
template: "newCharacterDialog",
element: event.currentTarget,
@@ -37,8 +59,23 @@ Template.characterList.events({
if (!character) return;
character.owner = Meteor.userId();
let _id = Characters.insert(character);
- Router.go("characterSheet", {_id});
+ let urlName = getSlug(character.name, {maintainCase: true}) || "-"
+ Router.go("characterSheet", {_id, urlName});
},
})
},
+ "click .addParty": function(event, instance) {
+ var partyId = Parties.insert({
+ owner: Meteor.userId(),
+ });
+ pushDialogStack({
+ template: "partyDialog",
+ data: {
+ _id: partyId,
+ startEditing: true,
+ },
+ element: event.currentTarget,
+ returnElement: instance.find(`.party[data-id='${partyId}']`),
+ });
+ },
});
diff --git a/rpg-docs/client/views/characterList/characterSideList.css b/rpg-docs/client/views/characterList/characterSideList.css
index e48e0b94..1b08159b 100644
--- a/rpg-docs/client/views/characterList/characterSideList.css
+++ b/rpg-docs/client/views/characterList/characterSideList.css
@@ -2,8 +2,21 @@
prevent character names from wrapping
*/
-.character-name {
+.side-list .character-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
+
+.side-list .partyHead {
+ font-weight: 500;
+ cursor: pointer;
+}
+
+.side-list .partyHead iron-icon {
+ transition: transform 0.3s ease;
+}
+
+.side-list .partyHead iron-icon.open {
+ transform: rotate(90deg);
+}
diff --git a/rpg-docs/client/views/characterList/characterSideList.html b/rpg-docs/client/views/characterList/characterSideList.html
index 38a19859..8a3fe6fd 100644
--- a/rpg-docs/client/views/characterList/characterSideList.html
+++ b/rpg-docs/client/views/characterList/characterSideList.html
@@ -1,15 +1,31 @@
- {{#if characters.count}}
-
- {{/if}}
+
diff --git a/rpg-docs/client/views/characterList/characterSideList.js b/rpg-docs/client/views/characterList/characterSideList.js
index 010905c2..18e4e800 100644
--- a/rpg-docs/client/views/characterList/characterSideList.js
+++ b/rpg-docs/client/views/characterList/characterSideList.js
@@ -1,33 +1,50 @@
Template.characterSideList.onCreated(function() {
this.subscribe("characterList");
+ this.openedParties = new ReactiveVar(new Set());
});
Template.characterSideList.helpers({
- characters: function() {
+ parties() {
+ var userId = Meteor.userId();
+ return Parties.find({owner: userId});
+ },
+ charactersInParty() {
var userId = Meteor.userId();
return Characters.find(
{
- $or: [
- {readers: userId},
- {writers: userId},
- {owner: userId},
- ]
+ _id: {$in: this.characters},
+ $or: [{readers: userId}, {writers: userId}, {owner: userId}],
},
- {
- fields: {name: 1, urlName: 1},
- sort: {name: 1},
- }
+ {sort: {name: 1}}
);
},
+ charactersWithNoParty() {
+ var userId = Meteor.userId();
+ var charArrays = Parties.find({owner: userId}).map(p => p.characters);
+ var partyChars = _.uniq(_.flatten(charArrays));
+ return Characters.find(
+ {
+ _id: {$nin: partyChars},
+ $or: [{readers: userId}, {writers: userId}, {owner: userId}],
+ },
+ {sort: {name: 1}}
+ );
+ },
+ isOpen(id) {
+ var openedParties = Template.instance().openedParties.get();
+ console.log(openedParties);
+ return openedParties.has(id);
+ },
});
Template.characterSideList.events({
- "tap .singleLineItem": function(event, instance) {
- //Router.go("characterSheet", {_id: this._id});
- $("core-drawer-panel").get(0).closeDrawer();
- },
- "tap core-item": function() {
- Router.go("characterList");
- $("core-drawer-panel").get(0).closeDrawer();
+ "click .partyHead": function(event, instance){
+ var openedParties = instance.openedParties.get();
+ if (openedParties.has(this._id)){
+ openedParties.delete(this._id);
+ } else {
+ openedParties.add(this._id);
+ }
+ instance.openedParties.set(openedParties);
},
});
diff --git a/rpg-docs/client/views/characterList/partyDialog/partyDialog.css b/rpg-docs/client/views/characterList/partyDialog/partyDialog.css
new file mode 100644
index 00000000..f4ddf6ed
--- /dev/null
+++ b/rpg-docs/client/views/characterList/partyDialog/partyDialog.css
@@ -0,0 +1,3 @@
+.partyEdit .inPartyCheckbox {
+ margin-bottom: 8px;
+}
diff --git a/rpg-docs/client/views/characterList/partyDialog/partyDialog.html b/rpg-docs/client/views/characterList/partyDialog/partyDialog.html
new file mode 100644
index 00000000..9c6d1b86
--- /dev/null
+++ b/rpg-docs/client/views/characterList/partyDialog/partyDialog.html
@@ -0,0 +1,32 @@
+
+ {{#with party}}
+ {{#baseDialog title=name hideColor=true startEditing=true}}
+ {{> partyDetails}}
+ {{else}}
+ {{> partyEdit}}
+ {{/baseDialog}}
+ {{/with}}
+
+
+
+
+
+ {{#each character in getCharacters}}
+
{{character.name}}
+ {{/each}}
+
+
+
+
+
+
+
+
+ {{#each allCharacters}}
+
+ {{name}}
+
+ {{/each}}
+
+
diff --git a/rpg-docs/client/views/characterList/partyDialog/partyDialog.js b/rpg-docs/client/views/characterList/partyDialog/partyDialog.js
new file mode 100644
index 00000000..cb1c8e97
--- /dev/null
+++ b/rpg-docs/client/views/characterList/partyDialog/partyDialog.js
@@ -0,0 +1,62 @@
+Template.partyDialog.helpers({
+ party(){
+ return Parties.findOne(this._id);
+ }
+});
+
+Template.partyDetails.helpers({
+ getCharacters (){
+ var userId = Meteor.userId();
+ return Characters.find(
+ {
+ _id: {$in: this.characters},
+ $or: [{readers: userId}, {writers: userId}, {owner: userId}],
+ },
+ {sort: {name: 1}}
+ );
+ }
+});
+
+Template.partyEdit.helpers({
+ allCharacters() {
+ var userId = Meteor.userId();
+ return Characters.find(
+ {$or: [{readers: userId}, {writers: userId}, {owner: userId}]},
+ {sort: {name: 1}}
+ );
+ },
+ charInParty(charId) {
+ return _.contains(Template.parentData().characters, charId);
+ },
+});
+
+Template.partyDialog.events({
+ "click #deleteButton": function(event, instance){
+ Parties.remove(instance.data._id);
+ popDialogStack();
+ },
+ "click #doneEditingButton": function(event, instance){
+ popDialogStack();
+ },
+});
+
+Template.partyEdit.events({
+ "change .inPartyCheckbox": function(event, instance){
+ var currentCharacters = this.characters;
+ var checked = event.currentTarget.checked;
+ var charId = this._id;
+ var partyId = instance.data._id;
+ if (checked){
+ Parties.update(partyId, {$addToSet: {characters: charId}});
+ } else {
+ Parties.update(partyId, {$pull: {characters: charId}});
+ }
+ },
+ "input .partyNameInput": function(event, instance){
+ var name = event.currentTarget.value;
+ Parties.update(this._id, {$set: {name}}, {
+ removeEmptyStrings: false,
+ trimStrings: false,
+ });
+ },
+});
diff --git a/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.js b/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.js
index 09526a48..2d51ead3 100644
--- a/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.js
+++ b/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.js
@@ -4,11 +4,13 @@ Template.baseDialog.onCreated(function(){
Template.baseDialog.helpers({
editing: function(){
+ if (!Template.parentData() || !Template.parentData().charId) return true;
return Template.instance().editing.get() &&
canEditCharacter(Template.parentData().charId);
},
showEdit: function() {
if (this.hideEdit) return false;
+ if (!Template.parentData() || !Template.parentData().charId) return true;
return canEditCharacter(Template.parentData().charId);
},
});
diff --git a/rpg-docs/server/publications/characterList.js b/rpg-docs/server/publications/characterList.js
index 91e8e019..a33dd71d 100644
--- a/rpg-docs/server/publications/characterList.js
+++ b/rpg-docs/server/publications/characterList.js
@@ -4,27 +4,24 @@ Meteor.publish("characterList", function(){
this.ready();
return;
}
- return Characters.find(
- {
- $or: [
- {readers: userId},
- {writers: userId},
- {owner: userId},
- ]
- },
- {
- fields: {
- name: 1,
- urlName: 1,
- race: 1,
- alignment: 1,
- gender: 1,
- readers: 1,
- writers:1,
- owner: 1,
- color: 1,
- picture: 1,
+ return [
+ Characters.find(
+ {$or: [{readers: userId}, {writers: userId}, {owner: userId}]},
+ {
+ fields: {
+ name: 1,
+ urlName: 1,
+ race: 1,
+ alignment: 1,
+ gender: 1,
+ readers: 1,
+ writers:1,
+ owner: 1,
+ color: 1,
+ picture: 1,
+ }
}
- }
- );
+ ),
+ Parties.find({owner: userId}),
+ ];
});