Merge branch 'master' into feature-custom-buffs
This commit is contained in:
@@ -83,3 +83,170 @@ Spells.after.update(function (userId, spell, fieldNames) {
|
|||||||
|
|
||||||
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||||
Spells.deny(CHARACTER_SUBSCHEMA_DENY);
|
Spells.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var checkMovePermission = function(spellId, parent, destinationOnly) {
|
||||||
|
var spell = Spells.findOne(spellId);
|
||||||
|
if (!spell)
|
||||||
|
throw new Meteor.Error("No such spell",
|
||||||
|
"An spell could not be found to move");
|
||||||
|
//handle permissions
|
||||||
|
var permission;
|
||||||
|
|
||||||
|
if (!destinationOnly) { //if we're not modifying the origin, only the destination
|
||||||
|
permission = Meteor.call("canWriteCharacter", spell.charId);
|
||||||
|
if (!permission){
|
||||||
|
throw new Meteor.Error("Access denied",
|
||||||
|
"Not permitted to move spells 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 spells 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 spells to this character");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var moveSpell = function(spellId, targetCollection, targetId) {
|
||||||
|
var spell = Spells.findOne(spellId);
|
||||||
|
if (!spell) return;
|
||||||
|
targetCollection = targetCollection || spell.parent.collection;
|
||||||
|
targetId = targetId || spell.parent.id;
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
checkMovePermission(spellId, {collection: targetCollection, id: targetId}, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetCollection == "Characters") { //then we are copying the spell to a different character.
|
||||||
|
targetList = SpellLists.findOne({"charId": targetId});
|
||||||
|
targetListId = targetList && targetList._id;
|
||||||
|
if (!targetListId) {
|
||||||
|
targetListId = SpellLists.insert({ //create a spell list if we don't already have one
|
||||||
|
name: "New SpellList",
|
||||||
|
charId: targetId,
|
||||||
|
saveDC: "8 + intelligenceMod + proficiencyBonus",
|
||||||
|
attackBonus: "intelligenceMod + proficiencyBonus",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Spells.update(
|
||||||
|
spellId,
|
||||||
|
{$set: {
|
||||||
|
charId: targetId,
|
||||||
|
"parent.collection": "SpellLists",
|
||||||
|
"parent.id": targetListId,
|
||||||
|
}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else { //we are moving the spell within the same character
|
||||||
|
//update the spell provided the update will actually change something
|
||||||
|
if (
|
||||||
|
spell.parent.collection !== targetCollection ||
|
||||||
|
spell.parent.id !== targetId
|
||||||
|
){
|
||||||
|
Spells.update(
|
||||||
|
spellId,
|
||||||
|
{$set: {
|
||||||
|
"parent.collection": targetCollection,
|
||||||
|
"parent.id": targetId,
|
||||||
|
}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var copySpell = function(spellId, targetCollection, targetId) {
|
||||||
|
var spell = Spells.findOne(spellId);
|
||||||
|
if (!spell) return;
|
||||||
|
targetCollection = targetCollection || spell.parent.collection;
|
||||||
|
targetId = targetId || spell.parent.id;
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
checkMovePermission(spellId, {collection: targetCollection, id: targetId}, true); //we're only reading from the source character
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (targetCollection == "Characters") { //then we are copying the spell to a different character.
|
||||||
|
targetList = SpellLists.findOne({"charId": targetId});
|
||||||
|
targetListId = targetList && targetList._id;
|
||||||
|
if (!targetListId) {
|
||||||
|
targetListId = SpellLists.insert({ //create a spell list if we don't already have one
|
||||||
|
name: "New SpellList",
|
||||||
|
charId: targetId,
|
||||||
|
saveDC: "8 + intelligenceMod + proficiencyBonus",
|
||||||
|
attackBonus: "intelligenceMod + proficiencyBonus",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
newSpell = _.clone(spell);
|
||||||
|
delete newSpell._id;
|
||||||
|
newSpellId = Spells.insert(newSpell); //add a new copy of the spell
|
||||||
|
Spells.update(
|
||||||
|
newSpellId,
|
||||||
|
{$set: {
|
||||||
|
charId: targetId,
|
||||||
|
"parent.collection": "SpellLists",
|
||||||
|
"parent.id": targetListId,
|
||||||
|
}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else { //else we are copying the spell within the same character
|
||||||
|
newSpell = _.clone(spell);
|
||||||
|
delete newSpell._id;
|
||||||
|
newSpellId = Spells.insert(newSpell); //add a new copy of the spell
|
||||||
|
Spells.update(
|
||||||
|
newSpellId,
|
||||||
|
{$set: {
|
||||||
|
"parent.collection": targetCollection,
|
||||||
|
"parent.id": targetId,
|
||||||
|
}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Meteor.methods({
|
||||||
|
moveSpellToList: function(spellId, spellListId) {
|
||||||
|
check(spellId, String);
|
||||||
|
check(spellListId, String);
|
||||||
|
moveSpell(spellId, "SpellLists", spellListId);
|
||||||
|
},
|
||||||
|
copySpellToList: function(spellId, spellListId) {
|
||||||
|
check(spellId, String);
|
||||||
|
check(spellListId, String);
|
||||||
|
copySpell(spellId, "SpellLists", spellListId);
|
||||||
|
},
|
||||||
|
moveSpellToCharacter: function(spellId, charId) {
|
||||||
|
check(spellId, String);
|
||||||
|
check(charId, String);
|
||||||
|
moveSpell(spellId, "Characters", charId);
|
||||||
|
},
|
||||||
|
copySpellToCharacter: function(spellId, charId) {
|
||||||
|
check(spellId, String);
|
||||||
|
check(charId, String);
|
||||||
|
copySpell(spellId, "Characters", charId);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<!-- shamelessly nicked and renamed from deleteCharacterConfirmation.html -->
|
||||||
|
<template name="unshareCharacterConfirmation">
|
||||||
|
<div class="fit layout vertical">
|
||||||
|
<app-header-layout has-scrolling-region class="feedback flex">
|
||||||
|
<app-header fixed effects="waterfall">
|
||||||
|
<app-toolbar>
|
||||||
|
<div main-title>Unshare Character</div>
|
||||||
|
</app-toolbar>
|
||||||
|
</app-header>
|
||||||
|
<div class="form flex">
|
||||||
|
Removing (unsharing) a character does not delete it.<br>
|
||||||
|
However, you will be no longer be able to access or view it, unless it is publicly visible.<br>
|
||||||
|
The character's owner or anyone with write permissions for the character can return read access.<br><br>
|
||||||
|
To continue type "{{name}}" into the box below.<br>
|
||||||
|
<paper-input id="nameInput" label="type the characters's name here" style="width: 100%;"></paper-input><br>
|
||||||
|
<paper-button id="unshareButton" style={{getStyle}} disabled={{cantUnshare}}>Unshare Character</paper-button>
|
||||||
|
</div>
|
||||||
|
</app-header-layout>
|
||||||
|
<div class="buttons layout horizontal end-justified">
|
||||||
|
<paper-button class="cancelButton"> Cancel </paper-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
Template.unshareCharacterConfirmation.onCreated(function() {
|
||||||
|
this.canUnshare = new ReactiveVar(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.unshareCharacterConfirmation.helpers({
|
||||||
|
cantUnshare: function() {
|
||||||
|
return !Template.instance().canUnshare.get();
|
||||||
|
},
|
||||||
|
getStyle: function() {
|
||||||
|
if (Template.instance().canUnshare.get()) {
|
||||||
|
return "background: #d23f31; color: white;";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Template.unshareCharacterConfirmation.events({
|
||||||
|
"change #nameInput, input #nameInput": function(event, instance) {
|
||||||
|
var can = instance.find("#nameInput").value === this.name;
|
||||||
|
instance.canUnshare.set(can);
|
||||||
|
},
|
||||||
|
"click #unshareButton": function(event, instance) {
|
||||||
|
if (instance.find("#nameInput").value === this.name) {
|
||||||
|
setTimeout(popDialogStack, 100); //weird things happen without the delay.
|
||||||
|
Router.go("/characterList");
|
||||||
|
Meteor.call("removeMeFromReaders", this._id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"click .cancelButton": function(event, instance){
|
||||||
|
popDialogStack();
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -30,6 +30,16 @@
|
|||||||
</paper-icon-item>
|
</paper-icon-item>
|
||||||
</paper-menu>
|
</paper-menu>
|
||||||
</paper-menu-button>
|
</paper-menu-button>
|
||||||
|
{{else}}
|
||||||
|
<paper-menu-button class="character-menu" horizontal-align="right">
|
||||||
|
<paper-icon-button icon="more-vert" class="dropdown-trigger"></paper-icon-button>
|
||||||
|
<paper-menu class="dropdown-content black87">
|
||||||
|
<paper-icon-item id="unshareCharacter">
|
||||||
|
<iron-icon icon="delete" item-icon></iron-icon>
|
||||||
|
Unshare
|
||||||
|
</paper-icon-item>
|
||||||
|
</paper-menu>
|
||||||
|
</paper-menu-button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div bottom-item>
|
<div bottom-item>
|
||||||
|
|||||||
@@ -210,4 +210,11 @@ Template.characterSheet.events({
|
|||||||
element: event.currentTarget.parentElement.parentElement,
|
element: event.currentTarget.parentElement.parentElement,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
"click #unshareCharacter": function(event, instance){
|
||||||
|
pushDialogStack({
|
||||||
|
data: this,
|
||||||
|
template: "unshareCharacterConfirmation",
|
||||||
|
element: event.currentTarget.parentElement.parentElement,
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,7 +36,9 @@
|
|||||||
|
|
||||||
{{> effectsViewList charId=charId parentId=_id}}
|
{{> effectsViewList charId=charId parentId=_id}}
|
||||||
{{> proficiencyViewList charId=charId parentId=_id}}
|
{{> proficiencyViewList charId=charId parentId=_id}}
|
||||||
|
{{> attacksViewList charId=charId parentId=_id}}
|
||||||
{{> customBuffViewList charId=charId parentId=_id}}
|
{{> customBuffViewList charId=charId parentId=_id}}
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="featureEdit">
|
<template name="featureEdit">
|
||||||
@@ -77,5 +79,6 @@
|
|||||||
|
|
||||||
{{> effectsEditList parentId=_id parentCollection="Features" charId=charId name=name enabled=enabled}}
|
{{> effectsEditList parentId=_id parentCollection="Features" charId=charId name=name enabled=enabled}}
|
||||||
{{> proficiencyEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled}}
|
{{> proficiencyEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled}}
|
||||||
|
{{> attackEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled name=name}}
|
||||||
{{> customBuffEditList parentId=_id parentCollection="Features" charId=charId}}
|
{{> customBuffEditList parentId=_id parentCollection="Features" charId=charId}}
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card.featureCard .bottom {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.containerMain.featureDescription {
|
.containerMain.featureDescription {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if description}}
|
{{#if hasCharacters (evaluateShortString charId description)}}
|
||||||
<div class="bottom flex">
|
<div class="bottom flex">
|
||||||
{{#markdown}}{{evaluateShortString charId description}}{{/markdown}}
|
{{#markdown}}{{evaluateShortString charId description}}{{/markdown}}
|
||||||
{{> customBuffViewList charId=charId parentId=_id}}
|
{{> customBuffViewList charId=charId parentId=_id}}
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ Template.features.helpers({
|
|||||||
var profs = Proficiencies.find({charId: this._id, type: "tool"});
|
var profs = Proficiencies.find({charId: this._id, type: "tool"});
|
||||||
return removeDuplicateProficiencies(profs);
|
return removeDuplicateProficiencies(profs);
|
||||||
},
|
},
|
||||||
|
hasCharacters: function(string){
|
||||||
|
return string.match(/\S/);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.features.events({
|
Template.features.events({
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
</paper-input>
|
</paper-input>
|
||||||
<paper-input id="valueInput" label="Value" type="number" value={{value}}>
|
<paper-input id="valueInput" label="Value" type="number" value={{value}}>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
|
<paper-toggle-button id="carriedToggle" checked={{isCarried}}>
|
||||||
|
Carried
|
||||||
|
</paper-toggle-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="vertMargin">
|
<hr class="vertMargin">
|
||||||
|
|||||||
@@ -54,4 +54,8 @@ Template.containerEdit.events({
|
|||||||
trimStrings: false,
|
trimStrings: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
"change #carriedToggle": function(event, instance){
|
||||||
|
var carried = !this.isCarried;
|
||||||
|
Containers.update(this._id, {$set: {isCarried: carried}});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -331,21 +331,23 @@ Template.layout.events({
|
|||||||
Session.set("inventory.dragItemId", null);
|
Session.set("inventory.dragItemId", null);
|
||||||
},
|
},
|
||||||
"drop .characterRepresentative": function(event, instance) {
|
"drop .characterRepresentative": function(event, instance) {
|
||||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||||
if (event.ctrlKey){
|
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||||
//split the stack to the container
|
if (event.ctrlKey){
|
||||||
pushDialogStack({
|
//split the stack to the container
|
||||||
template: "splitStackDialog",
|
pushDialogStack({
|
||||||
data: {
|
template: "splitStackDialog",
|
||||||
id: itemId,
|
data: {
|
||||||
parentCollection: "Characters",
|
id: itemId,
|
||||||
parentId: this._id,
|
parentCollection: "Characters",
|
||||||
},
|
parentId: this._id,
|
||||||
});
|
},
|
||||||
} else {
|
});
|
||||||
//move item to the character
|
} else {
|
||||||
Meteor.call("moveItemToCharacter", itemId, this._id);
|
//move item to the character
|
||||||
|
Meteor.call("moveItemToCharacter", itemId, this._id);
|
||||||
|
}
|
||||||
|
Session.set("inventory.dragItemId", null);
|
||||||
}
|
}
|
||||||
Session.set("inventory.dragItemId", null);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -83,7 +83,9 @@
|
|||||||
{{#each spells ../_id ../../_id}}
|
{{#each spells ../_id ../../_id}}
|
||||||
{{#if showSpell ../../_id}}
|
{{#if showSpell ../../_id}}
|
||||||
<div class="item-slot">
|
<div class="item-slot">
|
||||||
<div class="tall spell item layout horizontal center" data-id={{_id}}>
|
<div class="tall spell item layout horizontal center spellItem"
|
||||||
|
data-id={{_id}}
|
||||||
|
draggable={{canEditCharacter charId}}>
|
||||||
<iron-icon icon="social:whatshot"
|
<iron-icon icon="social:whatshot"
|
||||||
style="color: {{hexColor color}};
|
style="color: {{hexColor color}};
|
||||||
margin-right: 16px;"
|
margin-right: 16px;"
|
||||||
|
|||||||
@@ -333,3 +333,49 @@ Template.spells.events({
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Template.layout.events({
|
||||||
|
"dragstart .spellItem": function(event, instance){
|
||||||
|
event.originalEvent.dataTransfer.setData("dicecloud-id/spells", this._id);
|
||||||
|
Session.set("spellLists.dragSpellId", this._id);
|
||||||
|
},
|
||||||
|
"dragend .spellItem": function(event, instance){
|
||||||
|
Session.set("spellLists.dragSpellId", null);
|
||||||
|
},
|
||||||
|
|
||||||
|
"dragover .spellList, dragenter .spellList": function(event, instance){
|
||||||
|
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/spells")){
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"drop .spellList": function(event, instance){
|
||||||
|
var spellId = event.originalEvent.dataTransfer.getData("dicecloud-id/spells");
|
||||||
|
if (event.ctrlKey){
|
||||||
|
//copy spell to new list
|
||||||
|
Meteor.call("copySpellToList", spellId, this._id);
|
||||||
|
} else {
|
||||||
|
//move spell to new list
|
||||||
|
Meteor.call("moveSpellToList", spellId, this._id);
|
||||||
|
}
|
||||||
|
Session.set("spellLists.dragSpellId", null);
|
||||||
|
},
|
||||||
|
|
||||||
|
"dragover .characterRepresentative, dragenter .characterRepresentative": function(event, instance){
|
||||||
|
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/spells")){
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"drop .characterRepresentative": function(event, instance) {
|
||||||
|
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/spells")){ //to prevent conflicts with item drag/drop
|
||||||
|
var spellId = event.originalEvent.dataTransfer.getData("dicecloud-id/spells");
|
||||||
|
if (event.ctrlKey){
|
||||||
|
//copy spell to character
|
||||||
|
Meteor.call("copySpellToCharacter", spellId, this._id);
|
||||||
|
} else {
|
||||||
|
//move spell to character
|
||||||
|
Meteor.call("moveSpellToCharacter", spellId, this._id);
|
||||||
|
}
|
||||||
|
Session.set("spellLists.dragSpellId", null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
11
rpg-docs/lib/methods/removeMeFromReaders.js
Normal file
11
rpg-docs/lib/methods/removeMeFromReaders.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Meteor.methods({
|
||||||
|
removeMeFromReaders: function(charId) {
|
||||||
|
var userId = Meteor.userId();
|
||||||
|
var character = Characters.findOne(charId);
|
||||||
|
|
||||||
|
if (!character) return;
|
||||||
|
if (!_.contains(character.readers, userId)) return;
|
||||||
|
|
||||||
|
Characters.update(charId, {$pull: {"readers": userId}}); //we don't check write permission as you should always be able to remove youself from readers
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user