Compare commits

...

2 Commits
1.3.3 ... 1.3.4

Author SHA1 Message Date
Stefan Zermatten
2a332a2965 Merge branch 'feature-parties' 2017-07-14 17:09:47 +02:00
Stefan Zermatten
44a1daf6f8 Grouping characters by party now works
closes #75, finally
2017-07-14 17:09:30 +02:00
12 changed files with 358 additions and 104 deletions

View File

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

View File

@@ -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 {

View File

@@ -10,31 +10,27 @@
{{#if currentUser}}
{{#if characters.count}}
<div class="character-list layout horizontal wrap">
{{# each characters}}
<a class="character-card flex layout vertical end-justified" href="{{characterPath this}}">
<iron-image class="fit {{colorClass}}"
sizing="cover" preload fade src={{picture}}>
</iron-image>
{{#unless picture}}
<div class="fit initials layout vertical center center-justified">
{{initials name}}
</div>
{{/unless}}
<paper-item>
<paper-item-body two-lines>
<div class="name white87">
{{name}}
</div>
<div secondary style="color: #8a8a8a; color: rgba(255,255,255,0.87);">
{{alignment}} {{gender}} {{race}}
</div>
</paper-item-body>
</paper-item>
<paper-ripple></paper-ripple>
</a>
{{# each charactersWithNoParty}}
{{> characterCard}}
{{/each}}
{{> gridPadding class="character-card flex layout vertical" num=12}}
</div>
{{# each party in parties}}
<div class="party" data-id={{party._id}}>
{{#with party}}
<div class="partyHeader clickable paper-font-title padded">
{{name}}
<iron-icon icon="create"></iron-icon>
</div>
{{/with}}
<div class="character-list layout horizontal wrap">
{{# each charactersInParty party._id}}
{{> characterCard}}
{{/each}}
{{> gridPadding class="character-card flex layout vertical" num=12}}
</div>
</div>
{{/each}}
{{else}}
<div layout vertical center center-justified class="padded">
<div>You don't seem to have any characters yet</div>
@@ -47,9 +43,46 @@
</div>
{{/if}}
<div class="fab-buffer"></div>
<paper-fab class="floatyButton addCharacter"
icon="add"
title="Add"></paper-fab>
{{#fabMenu}}
<div>
<paper-fab icon="social:group"
class="addParty"
mini>
</paper-fab>
<paper-tooltip position="left"> New Party </paper-tooltip>
</div>
<div>
<paper-fab icon="face"
class="addCharacter"
mini>
</paper-fab>
<paper-tooltip position="left"> New Character </paper-tooltip>
</div>
{{/fabMenu}}
</div>
</app-header-layout>
</template>
<template name="characterCard">
<a class="character-card flex layout vertical end-justified" href="{{characterPath this}}">
<iron-image class="fit {{colorClass}}"
sizing="cover" preload fade src={{picture}}>
</iron-image>
{{#unless picture}}
<div class="fit initials layout vertical center center-justified">
{{initials name}}
</div>
{{/unless}}
<paper-item>
<paper-item-body two-lines>
<div class="name white87">
{{name}}
</div>
<div secondary style="color: #8a8a8a; color: rgba(255,255,255,0.87);">
{{alignment}} {{gender}} {{race}}
</div>
</paper-item-body>
</paper-item>
<paper-ripple></paper-ripple>
</a>
</template>

View File

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

View File

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

View File

@@ -1,15 +1,31 @@
<template name="characterSideList">
{{#if characters.count}}
<div class="side-list">
{{#each characters}}
<a href={{characterPath this}} tabindex="-1" class="side-list-character characterRepresentative">
<paper-item class="short">
<div class="character-name">
{{name}}
</div>
</paper-item>
</a>
{{/each}}
</div>
{{/if}}
<div class="side-list">
{{#each charactersWithNoParty}}
<a href={{characterPath this}} tabindex="-1" class="side-list-character characterRepresentative">
<paper-item class="short">
<div class="character-name">
{{name}}
</div>
</paper-item>
</a>
{{/each}}
{{#each parties}}
<div class="paper-font-subhead partyHead">
<iron-icon icon="chevron-right" class="{{#if isOpen _id}}open{{/if}}">
</iron-icon>
{{name}}
</div>
<iron-collapse opened={{isOpen _id}}>
{{#each charactersInParty}}
<a href={{characterPath this}} tabindex="-1" class="side-list-character characterRepresentative">
<paper-item class="short">
<div class="character-name">
{{name}}
</div>
</paper-item>
</a>
{{/each}}
</iron-collapse>
{{/each}}
</div>
</template>

View File

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

View File

@@ -0,0 +1,3 @@
.partyEdit .inPartyCheckbox {
margin-bottom: 8px;
}

View File

@@ -0,0 +1,32 @@
<template name="partyDialog">
{{#with party}}
{{#baseDialog title=name hideColor=true startEditing=true}}
{{> partyDetails}}
{{else}}
{{> partyEdit}}
{{/baseDialog}}
{{/with}}
</template>
<template name="partyDetails">
<div class="fit layout vertical partyDetails" style="padding: 24px;">
<div>
{{#each character in getCharacters}}
<div>{{character.name}}</div>
{{/each}}
</div>
</div>
</template>
<template name="partyEdit">
<div class="layout vertical partyEdit" style="padding: 24px;">
<paper-input class="partyNameInput" value={{name}} label="Party name">
</paper-input>
{{#each allCharacters}}
<paper-checkbox checked={{charInParty _id}}
class="inPartyCheckbox">
{{name}}
</paper-checkbox>
{{/each}}
</div>
</template>

View File

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

View File

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

View File

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