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 characters}} - - - - {{#unless picture}} -
- {{initials name}} -
- {{/unless}} - - -
- {{name}} -
-
- {{alignment}} {{gender}} {{race}} -
-
-
- -
+ {{# each charactersWithNoParty}} + {{> characterCard}} {{/each}} {{> gridPadding class="character-card flex layout vertical" num=12}}
+ {{# each party in parties}} +
+ {{#with party}} +
+ {{name}} + +
+ {{/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}} +
+ + + New Party +
+
+ + + New Character +
+ {{/fabMenu}} + + 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 @@ 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 @@ + + + + + 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}), + ]; });